From 14e9505314ac16d53db25bc5855e8683ecb29eac Mon Sep 17 00:00:00 2001 From: Rebecca Sutton Koeser Date: Thu, 20 Apr 2023 11:55:57 -0400 Subject: [PATCH 001/141] Initial commit --- .gitignore | 129 ++++++++++++++++++++++++++++++++++ LICENSE | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 1 + 3 files changed, 331 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6e4761 --- /dev/null +++ b/.gitignore @@ -0,0 +1,129 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..912d586 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# simulating-risk \ No newline at end of file From 26e3eb2ff8c2d02126df0f9a25d92ae0b20849cd Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Thu, 25 May 2023 12:10:18 -0400 Subject: [PATCH 002/141] Add basic setup instructions and configure pre-commit hooks --- .pre-commit-config.yaml | 12 ++++++++++++ README.md | 27 ++++++++++++++++++++++++++- requirements/dev.txt | 7 +++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 .pre-commit-config.yaml create mode 100644 requirements/dev.txt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..1bf3452 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,12 @@ +repos: + - repo: https://github.com/psf/black + rev: 23.3.0 # Replace by any tag/version: https://github.com/psf/black/tags + hooks: + - id: black + # Assumes that your shell's `python` command is linked to python3.6+ + language_version: python + - repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + args: ["--profile", "black", "--filter-files"] diff --git a/README.md b/README.md index 912d586..8ee8a60 100644 --- a/README.md +++ b/README.md @@ -1 +1,26 @@ -# simulating-risk \ No newline at end of file +# Simulating Risk + + +## Development instructions + +This git repository uses git flow branching conventions. + +Initial setup and installation: + +- *Recommmended*: create and activate a Python 3.9 virtualenv: +```sh +python3 -m venv simrisk +source simrisk/bin/activate +``` +- Install python dependencies:: +```sh +pip install -r requirements/dev.txt +``` + +### Install pre-commit hooks + +Install pre-commit hooks (currently [black](https://github.com/psf/black) and [isort](https://pycqa.github.io/isort/)): + +```sh +pre-commit install +``` diff --git a/requirements/dev.txt b/requirements/dev.txt new file mode 100644 index 0000000..c1facef --- /dev/null +++ b/requirements/dev.txt @@ -0,0 +1,7 @@ +# all requirements for production, test, and dev +#-r prod.txt +#-r test.txt +#-r docs.txt + +pre-commit +black From 6ec4b4e318bd56510ba72c21f2bcc2dece15aaa6 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Thu, 25 May 2023 12:01:12 -0400 Subject: [PATCH 003/141] Preliminary Mesa code for stag hunt game --- README.md | 10 ++-- requirements.txt | 1 + requirements/dev.txt | 2 +- requirements/main.txt | 0 stag_hunt/__init__.py | 0 stag_hunt/model.py | 123 ++++++++++++++++++++++++++++++++++++++++++ stag_hunt/run.py | 46 ++++++++++++++++ 7 files changed, 177 insertions(+), 5 deletions(-) create mode 100644 requirements.txt create mode 100644 requirements/main.txt create mode 100644 stag_hunt/__init__.py create mode 100644 stag_hunt/model.py create mode 100644 stag_hunt/run.py diff --git a/README.md b/README.md index 8ee8a60..385ca2a 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,7 @@ # Simulating Risk - ## Development instructions -This git repository uses git flow branching conventions. - Initial setup and installation: - *Recommmended*: create and activate a Python 3.9 virtualenv: @@ -20,7 +17,12 @@ pip install -r requirements/dev.txt ### Install pre-commit hooks Install pre-commit hooks (currently [black](https://github.com/psf/black) and [isort](https://pycqa.github.io/isort/)): - ```sh pre-commit install ``` + +Use Mesa runserver to run the prototype stag hunt model locally: +```sh +mesa runserver stag_hunt +``` + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e07f847 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +-r requirements/main.txt \ No newline at end of file diff --git a/requirements/dev.txt b/requirements/dev.txt index c1facef..8fd37ad 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,5 +1,5 @@ # all requirements for production, test, and dev -#-r prod.txt +-r main.txt #-r test.txt #-r docs.txt diff --git a/requirements/main.txt b/requirements/main.txt new file mode 100644 index 0000000..e69de29 diff --git a/stag_hunt/__init__.py b/stag_hunt/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/stag_hunt/model.py b/stag_hunt/model.py new file mode 100644 index 0000000..f517b28 --- /dev/null +++ b/stag_hunt/model.py @@ -0,0 +1,123 @@ +from enum import Enum +from functools import partial + +import mesa + +HuntChoice = Enum("Hunt", ["STAG", "HARE"]) +choices = [HuntChoice.STAG, HuntChoice.HARE] + + +class StagHuntAgent(mesa.Agent): + """An hunter agent who hunts stag or hare.""" + + def __init__(self, unique_id, model): + super().__init__(unique_id, model) + # self.wealth = 1 + # initialize hunt choice randomly for now + self.hunting = self.random.choices(list(HuntChoice), weights=[15, 85])[0] + # print("%s hunting %s" % (unique_id, self.hunting)) + # self.hunting = self.random.choice(choices) + self.last_payoff = None + + def payoff(self, other_agent): + # hunting hare has a payoff of 3 no matter what the other agent does + if self.hunting == HuntChoice.HARE: + return 3 + + # if both hunt stag, payoff is 4 + if other_agent.hunting == HuntChoice.STAG: + return 4 + + # hunting stag alone payoff is zero + return 0 + + def get_neighbors(self): + # use moore neighborhood (include diagonals), don't include self + return self.model.grid.get_neighbors(self.pos, True, False) + + def choose(self): + # decide on hunting strategy + # for first hunt, use initial strategy + + # if this is not the first time hunting, + # compare our payoff to neighbors + if self.last_payoff is not None: + neighbors = self.get_neighbors() + # update strategy for next time based on neighbors + # sort neighbors by their last payoff + neighbor_success = sorted( + neighbors, key=lambda n: n.last_payoff, reverse=True + ) + most_successful = neighbor_success[0] + if most_successful.last_payoff > self.last_payoff: + print( + "most successful neighbor: %s payoff=%s hunting=%s" + % ( + most_successful.unique_id, + most_successful.last_payoff, + most_successful.hunting, + ) + ) + self.hunting = most_successful.hunting + + def hunt(self): + # how to pair up agents? for now use random? + + neighbors = self.get_neighbors() + # choose hunting partner from neighbors randomly + other_agent = self.random.choice(neighbors) + + # other_agent = self.random.choice(self.model.schedule.agents) + self.last_payoff = self.payoff(other_agent) + # print("%s payoff %s" % (self.unique_id, self.last_payoff)) + + +def count_stag_hunters(model): + return len( + [agent for agent in model.schedule.agents if agent.hunting == HuntChoice.STAG] + ) + + +def num_hunting_choice(model, hunting): + return len( + [hunter for hunter in model.schedule.agents if hunter.hunting == hunting] + ) + + +class StagHuntModel(mesa.Model): + """A model with some number of stag-hunt agents.""" + + # def __init__(self, N): + # self.num_agents = N + def __init__(self, width, height): + self.num_agents = width * height + self.grid = mesa.space.SingleGrid(width, height, True) + self.schedule = mesa.time.StagedActivation(self, ["choose", "hunt"]) + # Create agents + for i in range(self.num_agents): + a = StagHuntAgent(i, self) + self.schedule.add(a) + # place randomly in an empty spot + self.grid.move_to_empty(a) + + # self.datacollector = mesa.DataCollector( + # model_reporters={"stag hunters": count_stag_hunters}, + # agent_reporters={"Hunting": "hunting", "Payoff": "last_payoff"}, + # ) + + self.datacollector = mesa.DataCollector( + model_reporters={ + "stag_hunters": partial( + num_hunting_choice, self, hunting=HuntChoice.STAG + ), + "hare_hunters": partial( + num_hunting_choice, self, hunting=HuntChoice.HARE + ), + } + ) + self.datacollector.collect(self) + + def step(self): + """Advance the model by one step.""" + self.datacollector.collect(self) + self.schedule.step() diff --git a/stag_hunt/run.py b/stag_hunt/run.py new file mode 100644 index 0000000..7d3cf9d --- /dev/null +++ b/stag_hunt/run.py @@ -0,0 +1,46 @@ +import mesa + +from stag_hunt.model import HuntChoice, StagHuntModel + + +def agent_portrayal(agent): + # TODO: figure out where Mesa wants this import to happen + # (expects model and server nested deeper than run?) + from stag_hunt.model import HuntChoice, StagHuntModel + + portrayal = { + "Shape": "circle", + "Color": "gray", + "Filled": "true", + "Layer": 0, + "r": 0.2, + } + + if agent.hunting == HuntChoice.STAG: + portrayal["Color"] = "green" + else: + portrayal["Color"] = "blue" + + if agent.last_payoff == 0: + portrayal["r"] = 0.2 + elif agent.last_payoff == 3: + portrayal["r"] = 0.4 + elif agent.last_payoff == 4: + portrayal["r"] = 0.7 + return portrayal + + +grid = mesa.visualization.CanvasGrid(agent_portrayal, 20, 20, 500, 500) +chart = mesa.visualization.ChartModule( + [ + {"Label": "stag_hunters", "Color": "green"}, + {"Label": "hare_hunters", "Color": "blue"}, + ], + data_collector_name="datacollector", +) + +server = mesa.visualization.ModularServer( + StagHuntModel, [grid, chart], "Stag Hunt Model", {"width": 20, "height": 20} +) +server.port = 8521 # The default +server.launch() From 15608bbc93ee1f0afe0bef834110ad1da590997c Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Thu, 1 Jun 2023 17:43:08 -0400 Subject: [PATCH 004/141] Preliminary model for risky food simulation #3 --- risky_food/__init__.py | 0 risky_food/model.py | 121 +++++++++++++++++++++++++++++++++++++++++ risky_food/run.py | 17 ++++++ 3 files changed, 138 insertions(+) create mode 100644 risky_food/__init__.py create mode 100644 risky_food/model.py create mode 100644 risky_food/run.py diff --git a/risky_food/__init__.py b/risky_food/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/risky_food/model.py b/risky_food/model.py new file mode 100644 index 0000000..4301b5d --- /dev/null +++ b/risky_food/model.py @@ -0,0 +1,121 @@ +from enum import Enum +from functools import partial + +import mesa + + +class FoodChoice(Enum): + RISKY = "R" + SAFE = "S" + + +class FoodStatus(Enum): + CONTAMINATED = "C" + NOTCONTAMINATED = "N" + + +class Agent(mesa.Agent): + def __init__(self, unique_id, model, risk_level=None): + super().__init__(unique_id, model) + # get a random risk tolerance; returns a value between 0.0 and 1.0 + self.risk_level = risk_level or self.random.random() + print(f"agent {unique_id} risk level {self.risk_level}") + + def step(self): + # choose food based on the probability not contaminated and risk tolerance + if self.risk_level > self.model.prob_notcontaminated: + choice = FoodChoice.RISKY + else: + choice = FoodChoice.SAFE + self.payoff = self.model.payoff(choice) + print( + f"agent {self.unique_id} r {self.risk_level:.4f} p {self.model.prob_notcontaminated:.4f} choice: {choice} payoff {self.payoff}" + ) + + +def food_status(model): + if model.risky_food_status == FoodStatus.CONTAMINATED: + print("food status 1") + return 1 + print("food status 0") + return 0 + + +class RiskyFoodModel(mesa.Model): + prob_notcontaminated = None + + def __init__(self, n): + self.num_agents = n + self.schedule = mesa.time.SimultaneousActivation(self) + # initialize agents for the first round + for i in range(self.num_agents): + a = Agent(i, self) + self.schedule.add(a) + + self.nextid = i + 1 + + self.datacollector = mesa.DataCollector( + model_reporters={ + "prob_notcontaminated": "prob_notcontaminated", + "contaminated": "contaminated", + } + # TODO: add data collection for agents to track risk level + ) + + def step(self): + """Advance the model by one step.""" + # pick a probability for risky food being not contaminated this round + self.prob_notcontaminated = self.random.random() + # determine actual food status, weighted by probability of non-contamination + print([self.prob_notcontaminated, 1 - self.prob_notcontaminated]) + # randomly choose based on probabality not contaminated; return the first choice + self.risky_food_status = self.random.choices( + [FoodStatus.NOTCONTAMINATED, FoodStatus.CONTAMINATED], + weights=[self.prob_notcontaminated, 1 - self.prob_notcontaminated], + )[0] + print( + f"p not contaminated: {self.prob_notcontaminated:.4f} actual status: {self.risky_food_status}" + ) + self.schedule.step() + self.datacollector.collect(self) + + # setup agents for the next round + self.propagate() + + def propagate(self): + # update agents based on payoff from the completed round + + # get a generator of agents from the scheduler that + # will allow us to add and remove + for agent in self.schedule.agent_buffer(): + # add offspring based on payoff; keep risk level + for i in range(agent.payoff): + a = Agent(i + self.nextid, self, agent.risk_level) + self.schedule.add(a) + # remove agent from previous round + self.schedule.remove(agent) + + self.nextid += agent.payoff + + print(f"finished propagation, {self.schedule.get_agent_count()} total agents") + + @property + def contaminated(self): + # return a value for food status this round, for data collection + if self.risky_food_status == FoodStatus.CONTAMINATED: + return 1 + return 0 + + def payoff(self, choice): + "Calculate the payoff for a given choice, based on current food status" + + # safe food choice always has a payoff of 2 + if choice == FoodChoice.SAFE: + return 2 + # payoff for risky food choice depends on contamination + # - if not contaminated, payoff of 3 + if self.risky_food_status == FoodStatus.NOTCONTAMINATED: + return 3 + + # otherwise only payoff of 1 + return 1 diff --git a/risky_food/run.py b/risky_food/run.py new file mode 100644 index 0000000..4618529 --- /dev/null +++ b/risky_food/run.py @@ -0,0 +1,17 @@ +import mesa + +from risky_food.model import RiskyFoodModel + +chart = mesa.visualization.ChartModule( + [ + {"Label": "prob_notcontaminated", "Color": "blue"}, + {"Label": "contaminated", "Color": "red"}, + ], + data_collector_name="datacollector", +) + +server = mesa.visualization.ModularServer( + RiskyFoodModel, [chart], "Risky Food", {"n": 20} +) +server.port = 8521 # The default +server.launch() From 33cd8e73b184dcd0832ee37b8f379771dbc1871b Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Thu, 1 Jun 2023 17:45:21 -0400 Subject: [PATCH 005/141] Add python requirements --- requirements/dev.txt | 2 +- requirements/main.txt | 0 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 requirements/main.txt diff --git a/requirements/dev.txt b/requirements/dev.txt index c1facef..8fd37ad 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,5 +1,5 @@ # all requirements for production, test, and dev -#-r prod.txt +-r main.txt #-r test.txt #-r docs.txt diff --git a/requirements/main.txt b/requirements/main.txt new file mode 100644 index 0000000..e69de29 From 2c278066ce3d95ac7e5dd421e75061acff9f63f6 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 6 Jun 2023 14:09:17 -0400 Subject: [PATCH 006/141] Use ruff instead of isort --- .pre-commit-config.yaml | 11 ++++++----- README.md | 2 +- requirements/dev.txt | 1 - risky_food/model.py | 10 +++++++--- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1bf3452..d1eb8b7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,13 @@ repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.0.270 + hooks: + - id: ruff + args: [ --fix, --exit-non-zero-on-fix ] - repo: https://github.com/psf/black rev: 23.3.0 # Replace by any tag/version: https://github.com/psf/black/tags hooks: - id: black # Assumes that your shell's `python` command is linked to python3.6+ language_version: python - - repo: https://github.com/pycqa/isort - rev: 5.12.0 - hooks: - - id: isort - args: ["--profile", "black", "--filter-files"] diff --git a/README.md b/README.md index 8ee8a60..6cf4be0 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ pip install -r requirements/dev.txt ### Install pre-commit hooks -Install pre-commit hooks (currently [black](https://github.com/psf/black) and [isort](https://pycqa.github.io/isort/)): +Install pre-commit hooks (currently [black](https://github.com/psf/black) and [ruff](https://beta.ruff.rs/docs/)): ```sh pre-commit install diff --git a/requirements/dev.txt b/requirements/dev.txt index 8fd37ad..c576076 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -4,4 +4,3 @@ #-r docs.txt pre-commit -black diff --git a/risky_food/model.py b/risky_food/model.py index 4301b5d..13f2f37 100644 --- a/risky_food/model.py +++ b/risky_food/model.py @@ -1,5 +1,4 @@ from enum import Enum -from functools import partial import mesa @@ -28,8 +27,11 @@ def step(self): else: choice = FoodChoice.SAFE self.payoff = self.model.payoff(choice) + # debug output print( - f"agent {self.unique_id} r {self.risk_level:.4f} p {self.model.prob_notcontaminated:.4f} choice: {choice} payoff {self.payoff}" + f"agent {self.unique_id} r {self.risk_level:.4f} " + + f"p {self.model.prob_notcontaminated:.4f} " + + f"choice: {choice} payoff {self.payoff}" ) @@ -73,8 +75,10 @@ def step(self): [FoodStatus.NOTCONTAMINATED, FoodStatus.CONTAMINATED], weights=[self.prob_notcontaminated, 1 - self.prob_notcontaminated], )[0] + # debug output print( - f"p not contaminated: {self.prob_notcontaminated:.4f} actual status: {self.risky_food_status}" + f"p not contaminated: {self.prob_notcontaminated:.4f} " + + f"actual status: {self.risky_food_status}" ) self.schedule.step() self.datacollector.collect(self) From 168afb788131e0c8dc61c338fc08c15a1336e766 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 6 Jun 2023 14:12:03 -0400 Subject: [PATCH 007/141] Add readme to document the risky food simulation --- risky_food/README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 risky_food/README.md diff --git a/risky_food/README.md b/risky_food/README.md new file mode 100644 index 0000000..aee7a17 --- /dev/null +++ b/risky_food/README.md @@ -0,0 +1,26 @@ +# Risky Food Simulation + +## summary + +Game: risky food source is 3 if **N**, 1 if **C**; safe source is 2 + +N: +: non-contaminated +C: +: contaminated + +Every agent gets a parameter r between 0 and 1. [or DISCRETE: 8 buckets etc.] + +EACH ROUND: +- Nature selects a probability p for N +- For each agent: if r > p, then they choose RISKY; else SAFE +- Nature flips a coin with bias p for N, and announces N or C +- If N: everyone who chose RISKY gets 3, everyone who chose SAFE gets 2 +- If C: everyone who chose RISKY gets 1, everyone SAFE 2 +- Reproduce in proportion to payoff +- Either agent gets # of offspring = payoff [they replace–original “dies off”] + OR: take the total payoff for RISKYs over total for everyone, there are that proportion of RISKYs in the new population + +END ROUND + +SEE: We’ll see what are the risk attitudes that are replicated more and less over time \ No newline at end of file From 714b74c123270d60997c3f5c5966e09f342cf652 Mon Sep 17 00:00:00 2001 From: Rebecca Sutton Koeser Date: Tue, 6 Jun 2023 14:15:47 -0400 Subject: [PATCH 008/141] Improve formatting for game description --- risky_food/README.md | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/risky_food/README.md b/risky_food/README.md index aee7a17..a3ee1b0 100644 --- a/risky_food/README.md +++ b/risky_food/README.md @@ -4,23 +4,21 @@ Game: risky food source is 3 if **N**, 1 if **C**; safe source is 2 -N: -: non-contaminated -C: -: contaminated +- **N**: non-contaminated +- **C**: contaminated -Every agent gets a parameter r between 0 and 1. [or DISCRETE: 8 buckets etc.] +Every agent gets a parameter `r` between 0 and 1. [or DISCRETE: 8 buckets etc.] EACH ROUND: -- Nature selects a probability p for N -- For each agent: if r > p, then they choose RISKY; else SAFE -- Nature flips a coin with bias p for N, and announces N or C -- If N: everyone who chose RISKY gets 3, everyone who chose SAFE gets 2 -- If C: everyone who chose RISKY gets 1, everyone SAFE 2 +- Nature selects a probability `p` for **N** +- For each agent: if `r` > `p`, then they choose RISKY; else SAFE +- Nature flips a coin with bias `p` for **N**, and announces **N** or **C** +- If **N**: everyone who chose RISKY gets 3, everyone who chose SAFE gets 2 +- If **C**: everyone who chose RISKY gets 1, everyone SAFE 2 - Reproduce in proportion to payoff -- Either agent gets # of offspring = payoff [they replace–original “dies off”] - OR: take the total payoff for RISKYs over total for everyone, there are that proportion of RISKYs in the new population + - Either agent gets # of offspring = payoff [they replace–original “dies off”] + - OR: take the total payoff for RISKYs over total for everyone, there are that proportion of RISKYs in the new population END ROUND -SEE: We’ll see what are the risk attitudes that are replicated more and less over time \ No newline at end of file +SEE: We’ll see what are the risk attitudes that are replicated more and less over time From 3ce223ad28a82a5e12e94d19233599973ec6247a Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 6 Jun 2023 17:18:25 -0400 Subject: [PATCH 009/141] Document the risky food simulation --- risky_food/README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/risky_food/README.md b/risky_food/README.md index a3ee1b0..9d018c9 100644 --- a/risky_food/README.md +++ b/risky_food/README.md @@ -1,6 +1,6 @@ # Risky Food Simulation -## summary +## Summary Game: risky food source is 3 if **N**, 1 if **C**; safe source is 2 @@ -22,3 +22,11 @@ EACH ROUND: END ROUND SEE: We’ll see what are the risk attitudes that are replicated more and less over time + +## Running the simulation + +- Install python dependencies as described in the main project readme (requires mesa) +- To run from the main `simulating-risk` project directory: + - Configure python to include the current directory in import path; + for C-based shells, run `setenv PYTHONPATH .` ; for bash, run `export $PYTHONPATH=.` + - Run with mesa: `mesa runserver risky_food/` From 5058661e8bf4b1da8fe0cabfca13abfcbff46d72 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 6 Jun 2023 17:18:41 -0400 Subject: [PATCH 010/141] Add more data collection parameters --- risky_food/model.py | 66 +++++++++++++++++++++++++++++++++------------ risky_food/run.py | 17 +++++++++++- 2 files changed, 65 insertions(+), 18 deletions(-) diff --git a/risky_food/model.py b/risky_food/model.py index 13f2f37..0ca8380 100644 --- a/risky_food/model.py +++ b/risky_food/model.py @@ -1,4 +1,5 @@ from enum import Enum +from statistics import mean import mesa @@ -18,7 +19,7 @@ def __init__(self, unique_id, model, risk_level=None): super().__init__(unique_id, model) # get a random risk tolerance; returns a value between 0.0 and 1.0 self.risk_level = risk_level or self.random.random() - print(f"agent {unique_id} risk level {self.risk_level}") + # print(f"agent {unique_id} risk level {self.risk_level}") def step(self): # choose food based on the probability not contaminated and risk tolerance @@ -28,18 +29,18 @@ def step(self): choice = FoodChoice.SAFE self.payoff = self.model.payoff(choice) # debug output - print( - f"agent {self.unique_id} r {self.risk_level:.4f} " - + f"p {self.model.prob_notcontaminated:.4f} " - + f"choice: {choice} payoff {self.payoff}" - ) + # print( + # f"agent {self.unique_id} r {self.risk_level:.4f} " + # + f"p {self.model.prob_notcontaminated:.4f} " + # + f"choice: {choice} payoff {self.payoff}" + # ) def food_status(model): if model.risky_food_status == FoodStatus.CONTAMINATED: - print("food status 1") + # print("food status 1") return 1 - print("food status 0") + # print("food status 0") return 0 @@ -60,8 +61,12 @@ def __init__(self, n): model_reporters={ "prob_notcontaminated": "prob_notcontaminated", "contaminated": "contaminated", - } - # TODO: add data collection for agents to track risk level + "average_risk_level": "avg_risk_level", + "min_risk_level": "min_risk_level", + "max_risk_level": "max_risk_level", + "num_agents": "total_agents", + }, + agent_reporters={"risk_level": "risk_level", "payoff": "payoff"}, ) def step(self): @@ -69,17 +74,20 @@ def step(self): # pick a probability for risky food being not contaminated this round self.prob_notcontaminated = self.random.random() # determine actual food status, weighted by probability of non-contamination - print([self.prob_notcontaminated, 1 - self.prob_notcontaminated]) + # print([self.prob_notcontaminated, 1 - self.prob_notcontaminated]) # randomly choose based on probabality not contaminated; return the first choice + + notcontam_weight = self.prob_notcontaminated * 100 + self.risky_food_status = self.random.choices( [FoodStatus.NOTCONTAMINATED, FoodStatus.CONTAMINATED], - weights=[self.prob_notcontaminated, 1 - self.prob_notcontaminated], + weights=[notcontam_weight, 100 - notcontam_weight], )[0] # debug output - print( - f"p not contaminated: {self.prob_notcontaminated:.4f} " - + f"actual status: {self.risky_food_status}" - ) + # print( + # f"p not contaminated: {self.prob_notcontaminated:.4f} " + # + f"actual status: {self.risky_food_status}" + # ) self.schedule.step() self.datacollector.collect(self) @@ -101,7 +109,7 @@ def propagate(self): self.nextid += agent.payoff - print(f"finished propagation, {self.schedule.get_agent_count()} total agents") + # print(f"finished propagation, {self.schedule.get_agent_count()} total agents") @property def contaminated(self): @@ -110,6 +118,30 @@ def contaminated(self): return 1 return 0 + @property + def agents(self): + # custom property to make it easy to access all current agents + + # uses a generator of agents from the scheduler that + # will allow adding and removing agents from the scheduler + return self.schedule.agent_buffer() + + @property + def total_agents(self): + return len(list(self.agents)) + + @property + def avg_risk_level(self): + return mean([agent.risk_level for agent in self.agents]) + + @property + def min_risk_level(self): + return min([agent.risk_level for agent in self.agents]) + + @property + def max_risk_level(self): + return max([agent.risk_level for agent in self.agents]) + def payoff(self, choice): "Calculate the payoff for a given choice, based on current food status" diff --git a/risky_food/run.py b/risky_food/run.py index 4618529..926f733 100644 --- a/risky_food/run.py +++ b/risky_food/run.py @@ -9,9 +9,24 @@ ], data_collector_name="datacollector", ) +risk_chart = mesa.visualization.ChartModule( + [ + {"Label": "average_risk_level", "Color": "blue"}, + {"Label": "min_risk_level", "Color": "green"}, + {"Label": "max_risk_level", "Color": "orange"}, + ], + data_collector_name="datacollector", +) + +agent_chart = mesa.visualization.ChartModule( + [ + {"Label": "num_agents", "Color": "gray"}, + ], + data_collector_name="datacollector", +) server = mesa.visualization.ModularServer( - RiskyFoodModel, [chart], "Risky Food", {"n": 20} + RiskyFoodModel, [chart, risk_chart, agent_chart], "Risky Food", {"n": 20} ) server.port = 8521 # The default server.launch() From ffe21c16898030007b78325429b0eef62877cfff Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 6 Jun 2023 17:23:07 -0400 Subject: [PATCH 011/141] Make simrisk code pip installable --- README.md | 5 ++-- pyproject.toml | 26 +++++++++++++++++++ requirements/dev.txt | 6 ----- {risky_food => simulatingrisk}/__init__.py | 0 .../risky_food}/README.md | 2 +- .../risky_food/__init__.py | 0 .../risky_food}/model.py | 0 .../risky_food}/run.py | 2 +- 8 files changed, 31 insertions(+), 10 deletions(-) create mode 100644 pyproject.toml delete mode 100644 requirements/dev.txt rename {risky_food => simulatingrisk}/__init__.py (100%) rename {risky_food => simulatingrisk/risky_food}/README.md (93%) rename requirements/main.txt => simulatingrisk/risky_food/__init__.py (100%) rename {risky_food => simulatingrisk/risky_food}/model.py (100%) rename {risky_food => simulatingrisk/risky_food}/run.py (93%) diff --git a/README.md b/README.md index 6cf4be0..ad08e24 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,10 @@ Initial setup and installation: python3 -m venv simrisk source simrisk/bin/activate ``` -- Install python dependencies:: +- Install the package, dependencies, and development dependencies: ```sh -pip install -r requirements/dev.txt +pip install -e . +pip install -e ".[dev]" ``` ### Install pre-commit hooks diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..a3c7996 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,26 @@ +[build-system] +requires = ["setuptools", "setuptools-scm"] +build-backend = "setuptools.build_meta" + +[project] +name = "simulating_risk" +description = "Agent-based modeling for simulations related to risk and rationality" +readme = "README.md" +requires-python = ">=3.7" +license = {text = "Apache-2"} +classifiers = [ + "Programming Language :: Python :: 3", +] +dependencies = [ + "mesa", +] +dynamic = ["version"] + +[project.optional-dependencies] +dev = ["pre-commit"] + +[tool.black] +line-length = 88 +target-version = ['py38'] +# include = '' +# extend-exclude = '' diff --git a/requirements/dev.txt b/requirements/dev.txt deleted file mode 100644 index c576076..0000000 --- a/requirements/dev.txt +++ /dev/null @@ -1,6 +0,0 @@ -# all requirements for production, test, and dev --r main.txt -#-r test.txt -#-r docs.txt - -pre-commit diff --git a/risky_food/__init__.py b/simulatingrisk/__init__.py similarity index 100% rename from risky_food/__init__.py rename to simulatingrisk/__init__.py diff --git a/risky_food/README.md b/simulatingrisk/risky_food/README.md similarity index 93% rename from risky_food/README.md rename to simulatingrisk/risky_food/README.md index 9d018c9..ead54ee 100644 --- a/risky_food/README.md +++ b/simulatingrisk/risky_food/README.md @@ -29,4 +29,4 @@ SEE: We’ll see what are the risk attitudes that are replicated more and less o - To run from the main `simulating-risk` project directory: - Configure python to include the current directory in import path; for C-based shells, run `setenv PYTHONPATH .` ; for bash, run `export $PYTHONPATH=.` - - Run with mesa: `mesa runserver risky_food/` + - To run interactively with mesa runserver: `mesa runserver simulatingrisk/risky_food/` diff --git a/requirements/main.txt b/simulatingrisk/risky_food/__init__.py similarity index 100% rename from requirements/main.txt rename to simulatingrisk/risky_food/__init__.py diff --git a/risky_food/model.py b/simulatingrisk/risky_food/model.py similarity index 100% rename from risky_food/model.py rename to simulatingrisk/risky_food/model.py diff --git a/risky_food/run.py b/simulatingrisk/risky_food/run.py similarity index 93% rename from risky_food/run.py rename to simulatingrisk/risky_food/run.py index 926f733..35d6610 100644 --- a/risky_food/run.py +++ b/simulatingrisk/risky_food/run.py @@ -1,6 +1,6 @@ import mesa -from risky_food.model import RiskyFoodModel +from simulatingrisk.risky_food.model import RiskyFoodModel chart = mesa.visualization.ChartModule( [ From 95c762d15ff31218e97de0be1e27447258e000a2 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 6 Jun 2023 18:08:58 -0400 Subject: [PATCH 012/141] Initial unit testing structure --- pyproject.toml | 2 +- simulatingrisk/risky_food/model.py | 21 ++++++++++------- tests/test_risky_food.py | 36 ++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 9 deletions(-) create mode 100644 tests/test_risky_food.py diff --git a/pyproject.toml b/pyproject.toml index a3c7996..790a9fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ dependencies = [ dynamic = ["version"] [project.optional-dependencies] -dev = ["pre-commit"] +dev = ["pre-commit", "pytest"] [tool.black] line-length = 88 diff --git a/simulatingrisk/risky_food/model.py b/simulatingrisk/risky_food/model.py index 0ca8380..7c19dfc 100644 --- a/simulatingrisk/risky_food/model.py +++ b/simulatingrisk/risky_food/model.py @@ -73,16 +73,10 @@ def step(self): """Advance the model by one step.""" # pick a probability for risky food being not contaminated this round self.prob_notcontaminated = self.random.random() - # determine actual food status, weighted by probability of non-contamination - # print([self.prob_notcontaminated, 1 - self.prob_notcontaminated]) - # randomly choose based on probabality not contaminated; return the first choice - notcontam_weight = self.prob_notcontaminated * 100 + self.prob_notcontaminated * 100 - self.risky_food_status = self.random.choices( - [FoodStatus.NOTCONTAMINATED, FoodStatus.CONTAMINATED], - weights=[notcontam_weight, 100 - notcontam_weight], - )[0] + self.risky_food_status = self.get_risky_food_status() # debug output # print( # f"p not contaminated: {self.prob_notcontaminated:.4f} " @@ -94,6 +88,17 @@ def step(self): # setup agents for the next round self.propagate() + def get_risky_food_status(self): + # determine actual food status for this round, + # weighted by probability of non-contamination + + # randomly choose, with choice weighted by + # current probability not contaminated + return self.random.choices( + [FoodStatus.NOTCONTAMINATED, FoodStatus.CONTAMINATED], + weights=[self.prob_notcontaminated, 1 - self.prob_notcontaminated], + )[0] + def propagate(self): # update agents based on payoff from the completed round diff --git a/tests/test_risky_food.py b/tests/test_risky_food.py new file mode 100644 index 0000000..c5e7c49 --- /dev/null +++ b/tests/test_risky_food.py @@ -0,0 +1,36 @@ +from collections import Counter +import math +import pytest + +from simulatingrisk.risky_food.model import RiskyFoodModel, FoodStatus + + +test_probabilities = [ + (0.5), + (0.2), + (0.8), +] + + +@pytest.mark.parametrize("prob_notcontaminated", test_probabilities) +def test_risky_food_status(prob_notcontaminated): + # test that food status choice is weighted properly + # by probability of not being contaminated + + # initialize model with one agent + model = RiskyFoodModel(1) + model.prob_notcontaminated = prob_notcontaminated + + results = [] + total_runs = 100 + for i in range(total_runs): + results.append(model.get_risky_food_status()) + + # use counter to tally the results + result_count = Counter(results) + + # the expected value is the probability times number of times we ran it + expected = total_runs * model.prob_notcontaminated + assert math.isclose( + result_count[FoodStatus.NOTCONTAMINATED], expected, abs_tol=total_runs * 0.1 + ) From 9f3374baaad793fed084a1966014f8d51e61a428 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 6 Jun 2023 18:15:26 -0400 Subject: [PATCH 013/141] Preliminary github actions unit test workflow --- .github/workflows/unit_tests.yml | 64 ++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/unit_tests.yml diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml new file mode 100644 index 0000000..5740fdc --- /dev/null +++ b/.github/workflows/unit_tests.yml @@ -0,0 +1,64 @@ +name: unit tests + +on: + push: # run on every push or PR to any branch + pull_request: + +jobs: + python-unit: + name: Python unit tests + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + + # base the python cache on the hash of all pyproject.toml, + # which includes python requirements. + # if any change, the cache is invalidated. + - name: Cache pip + uses: actions/cache@v2 + with: + path: ~/.cache/pip + key: pip-${{ hashFiles('pyproject.toml') }} + restore-keys: | + pip-${{ hashFiles('pyproject.toml') }} + pip- + + - name: Install dependencies + run: pip install ".[dev]" + + - name: Run pytest + run: pytest --cov=./ --cov-report=xml + + - name: Upload test coverage to Codecov + uses: codecov/codecov-action@v3 + + # Set the color of the slack message used in the next step based on the + # status of the build: "danger" for failure, "good" for success, + # "warning" for error + - name: Set Slack message color based on build status + if: ${{ always() }} + env: + JOB_STATUS: ${{ job.status }} + run: echo "SLACK_COLOR=$(if [ "$JOB_STATUS" == "success" ]; then echo "good"; elif [ "$JOB_STATUS" == "failure" ]; then echo "danger"; else echo "warning"; fi)" >> $GITHUB_ENV + + # Send a message to slack to report the build status. The webhook is stored + # at the organization level and available to all repositories. Only run on + # scheduled builds & pushes, since PRs automatically report to Slack. + - name: Report status to Slack + uses: rtCamp/action-slack-notify@master + if: ${{ always() && (github.event_name == 'schedule' || github.event_name == 'push') }} + continue-on-error: true + env: + SLACK_COLOR: ${{ env.SLACK_COLOR }} + SLACK_WEBHOOK: ${{ secrets.ACTIONS_SLACK_WEBHOOK }} + SLACK_TITLE: "Workflow `${{ github.workflow }}`: ${{ job.status }}" + SLACK_MESSAGE: "Run on " + SLACK_FOOTER: "" + MSG_MINIMAL: true # use compact slack message format diff --git a/pyproject.toml b/pyproject.toml index 790a9fe..3abec88 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ dependencies = [ dynamic = ["version"] [project.optional-dependencies] -dev = ["pre-commit", "pytest"] +dev = ["pre-commit", "pytest", "pytest-cov"] [tool.black] line-length = 88 From c07b522d7ba75185bfdfa41237449c57084ffaee Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 6 Jun 2023 18:17:46 -0400 Subject: [PATCH 014/141] Update deprecated action versions --- .github/workflows/unit_tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 5740fdc..13f6fde 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -14,7 +14,7 @@ jobs: uses: actions/checkout@v3 - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: 3.9 @@ -22,7 +22,7 @@ jobs: # which includes python requirements. # if any change, the cache is invalidated. - name: Cache pip - uses: actions/cache@v2 + uses: actions/cache@v3 with: path: ~/.cache/pip key: pip-${{ hashFiles('pyproject.toml') }} From e112e4eb1f9eb38b704008b6f228250e71b232ea Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 6 Jun 2023 18:20:37 -0400 Subject: [PATCH 015/141] Clean up debug output and unused code --- simulatingrisk/risky_food/model.py | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/simulatingrisk/risky_food/model.py b/simulatingrisk/risky_food/model.py index 7c19dfc..95163c0 100644 --- a/simulatingrisk/risky_food/model.py +++ b/simulatingrisk/risky_food/model.py @@ -19,7 +19,6 @@ def __init__(self, unique_id, model, risk_level=None): super().__init__(unique_id, model) # get a random risk tolerance; returns a value between 0.0 and 1.0 self.risk_level = risk_level or self.random.random() - # print(f"agent {unique_id} risk level {self.risk_level}") def step(self): # choose food based on the probability not contaminated and risk tolerance @@ -28,20 +27,6 @@ def step(self): else: choice = FoodChoice.SAFE self.payoff = self.model.payoff(choice) - # debug output - # print( - # f"agent {self.unique_id} r {self.risk_level:.4f} " - # + f"p {self.model.prob_notcontaminated:.4f} " - # + f"choice: {choice} payoff {self.payoff}" - # ) - - -def food_status(model): - if model.risky_food_status == FoodStatus.CONTAMINATED: - # print("food status 1") - return 1 - # print("food status 0") - return 0 class RiskyFoodModel(mesa.Model): @@ -77,11 +62,7 @@ def step(self): self.prob_notcontaminated * 100 self.risky_food_status = self.get_risky_food_status() - # debug output - # print( - # f"p not contaminated: {self.prob_notcontaminated:.4f} " - # + f"actual status: {self.risky_food_status}" - # ) + self.schedule.step() self.datacollector.collect(self) @@ -114,8 +95,6 @@ def propagate(self): self.nextid += agent.payoff - # print(f"finished propagation, {self.schedule.get_agent_count()} total agents") - @property def contaminated(self): # return a value for food status this round, for data collection From 97f9278b03daa80f103df10af08f4e83114073e8 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 14 Jun 2023 11:27:31 -0400 Subject: [PATCH 016/141] Adjust propagation logic for next round --- simulatingrisk/risky_food/model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/simulatingrisk/risky_food/model.py b/simulatingrisk/risky_food/model.py index 95163c0..798972c 100644 --- a/simulatingrisk/risky_food/model.py +++ b/simulatingrisk/risky_food/model.py @@ -87,11 +87,11 @@ def propagate(self): # will allow us to add and remove for agent in self.schedule.agent_buffer(): # add offspring based on payoff; keep risk level - for i in range(agent.payoff): + # logic is offspring = to payoff, original dies off, + # but for efficiency just add payoff - 1 and keep the original + for i in range(agent.payoff - 1): a = Agent(i + self.nextid, self, agent.risk_level) self.schedule.add(a) - # remove agent from previous round - self.schedule.remove(agent) self.nextid += agent.payoff From 751fd5531a4c201bcf6178153267f99350029884 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 14 Jun 2023 15:28:08 -0400 Subject: [PATCH 017/141] Move stag hunt prototype into simulatingrisk project dir --- {stag_hunt => simulatingrisk/stag_hunt}/__init__.py | 0 {stag_hunt => simulatingrisk/stag_hunt}/model.py | 0 {stag_hunt => simulatingrisk/stag_hunt}/run.py | 1 - 3 files changed, 1 deletion(-) rename {stag_hunt => simulatingrisk/stag_hunt}/__init__.py (100%) rename {stag_hunt => simulatingrisk/stag_hunt}/model.py (100%) rename {stag_hunt => simulatingrisk/stag_hunt}/run.py (99%) diff --git a/stag_hunt/__init__.py b/simulatingrisk/stag_hunt/__init__.py similarity index 100% rename from stag_hunt/__init__.py rename to simulatingrisk/stag_hunt/__init__.py diff --git a/stag_hunt/model.py b/simulatingrisk/stag_hunt/model.py similarity index 100% rename from stag_hunt/model.py rename to simulatingrisk/stag_hunt/model.py diff --git a/stag_hunt/run.py b/simulatingrisk/stag_hunt/run.py similarity index 99% rename from stag_hunt/run.py rename to simulatingrisk/stag_hunt/run.py index 7d3cf9d..9f2cfe0 100644 --- a/stag_hunt/run.py +++ b/simulatingrisk/stag_hunt/run.py @@ -1,5 +1,4 @@ import mesa - from stag_hunt.model import HuntChoice, StagHuntModel From 9a130cb340d4c7209587c935bc0f2ea6defcba9f Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 14 Jun 2023 16:21:35 -0400 Subject: [PATCH 018/141] Add a minimal readme for stag hunt sim, documenting incomplete status --- simulatingrisk/stag_hunt/README.md | 15 +++++++++++++++ simulatingrisk/stag_hunt/run.py | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 simulatingrisk/stag_hunt/README.md diff --git a/simulatingrisk/stag_hunt/README.md b/simulatingrisk/stag_hunt/README.md new file mode 100644 index 0000000..9f577a8 --- /dev/null +++ b/simulatingrisk/stag_hunt/README.md @@ -0,0 +1,15 @@ +# Stag Hunt Simulation + +## Summary + +This is a preliminary, incomplete implementation of the Stag Hunt game, drawing on Skyrms' _The stag hunt and the evolution of social structure_ (2004). + +With the current implementation and payoff scheme, it converges to everyone hunting stag fairly quickly, and is probably not very interesting; but it may useful as a reference with mesa simulations, or for further refinement and experimentation. + +## Running the simulation + +- Install python dependencies as described in the main project readme (requires mesa) +- To run from the main `simulating-risk` project directory: + - Configure python to include the current directory in import path; + for C-based shells, run `setenv PYTHONPATH .` ; for bash, run `export $PYTHONPATH=.` + - To run interactively with mesa runserver: `mesa runserver simulatingrisk/stag_hunt/` diff --git a/simulatingrisk/stag_hunt/run.py b/simulatingrisk/stag_hunt/run.py index 9f2cfe0..239ea93 100644 --- a/simulatingrisk/stag_hunt/run.py +++ b/simulatingrisk/stag_hunt/run.py @@ -1,11 +1,11 @@ import mesa -from stag_hunt.model import HuntChoice, StagHuntModel +from simulatingrisk.stag_hunt.model import StagHuntModel def agent_portrayal(agent): # TODO: figure out where Mesa wants this import to happen # (expects model and server nested deeper than run?) - from stag_hunt.model import HuntChoice, StagHuntModel + from simulatingrisk.stag_hunt.model import HuntChoice portrayal = { "Shape": "circle", From 1342ef54cf5c4501f60c1d329991e9ab977b7ebf Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 14 Jun 2023 17:08:11 -0400 Subject: [PATCH 019/141] Preliminary implementation of risky bet game #6 --- simulatingrisk/risky_bet/model.py | 99 +++++++++++++++++++++++++++++++ simulatingrisk/risky_bet/run.py | 67 +++++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 simulatingrisk/risky_bet/model.py create mode 100644 simulatingrisk/risky_bet/run.py diff --git a/simulatingrisk/risky_bet/model.py b/simulatingrisk/risky_bet/model.py new file mode 100644 index 0000000..ffed8ce --- /dev/null +++ b/simulatingrisk/risky_bet/model.py @@ -0,0 +1,99 @@ +from enum import Enum + +import mesa + +Bet = Enum("Bet", ["RISKY", "SAFE"]) +bet_choices = [Bet.SAFE, Bet.RISKY] + + +class RiskyGambler(mesa.Agent): + def __init__(self, unique_id, model, initial_wealth): + super().__init__(unique_id, model) + # starting wealth determined by the model + self.wealth = initial_wealth + # get a random risk tolerance; returns a value between 0.0 and 1.0 + self.risk_level = self.random.random() + self.choice = None + + def step(self): + # decide how to bet based on risk tolerance and likelihood + # that the risky bet will pay off + if self.risk_level > self.model.prob_risky_payoff: + self.choice = Bet.RISKY + else: + self.choice = Bet.SAFE + + # determine the payoff for this choice + self.calculate_payoff(self.choice) + + def calculate_payoff(self, choice): + if choice == Bet.RISKY: + # if the risky bet paid off, multiply current wealth by 1.5 + if self.model.risky_payoff: + self.wealth = self.wealth * 1.5 + print( + "agent %s risky bet paid off, wealth is now %s" + % (self.unique_id, self.wealth) + ) + + # if it doesn't, multiply by 0.5 + else: + self.wealth = self.wealth * 0.5 + print( + "agent %s risky bet did not paid off, wealth is now %s" + % (self.unique_id, self.wealth) + ) + # safe choice = wealth stays the same + print("agent %s no bet, wealth stays %s" % (self.unique_id, self.wealth)) + + +class RiskyBetModel(mesa.Model): + initial_wealth = 1000 + + def __init__(self, grid_size): + # assume a fully-populated square grid + self.num_agents = grid_size * grid_size + # initialize a single grid (each square inhabited by a single agent); + # configure the grid to wrap around so everyone has neighbors + self.grid = mesa.space.SingleGrid(grid_size, grid_size, True) + self.schedule = mesa.time.SimultaneousActivation(self) + + # initialize agents and add to grid and scheduler + for i in range(self.num_agents): + a = RiskyGambler(i, self, self.initial_wealth) + self.schedule.add(a) + # place randomly in an empty spot + self.grid.move_to_empty(a) + + self.datacollector = mesa.DataCollector( + # TODO: figure out what data to collect + model_reporters={ + # "prob_notcontaminated": "prob_notcontaminated", + # "contaminated": "contaminated", + # "average_risk_level": "avg_risk_level", + # "min_risk_level": "min_risk_level", + # "max_risk_level": "max_risk_level", + # "num_agents": "total_agents", + }, + agent_reporters={"risk_level": "risk_level", "choice": "choice"}, + ) + + def step(self): + # run a single round of the game + + # determine the probability of the risky bet paying off this round + self.prob_risky_payoff = self.random.random() + # determine whether it actually pays off + self.risky_payoff = self.call_risky_bet() + + self.schedule.step() + # TODO: add periodic agent risk adjustment (every ten rounds) + self.datacollector.collect(self) + + def call_risky_bet(self): + # flip a weighted coin to determine if the risky bet pays off, + # weighted by current round payoff probability + return self.random.choices( + [True, False], + weights=[self.prob_risky_payoff, 1 - self.prob_risky_payoff], + )[0] diff --git a/simulatingrisk/risky_bet/run.py b/simulatingrisk/risky_bet/run.py new file mode 100644 index 0000000..7b5793d --- /dev/null +++ b/simulatingrisk/risky_bet/run.py @@ -0,0 +1,67 @@ +import mesa + +from simulatingrisk.risky_bet.model import RiskyBetModel + + +def agent_portrayal(agent): + import math + + # divergent color scheme, ten colors + # from https://colorbrewer2.org/#type=diverging&scheme=RdYlGn&n=10 + colors = [ + "#a50026", + "#d73027", + "#f46d43", + "#fdae61", + "#fee08b", + "#d9ef8b", + "#a6d96a", + "#66bd63", + "#1a9850", + "#006837", + ] + + portrayal = { + "Shape": "circle", + "Color": "gray", + "Filled": "true", + "Layer": 0, + "r": 0.5, + } + + # color based on risk level, with ten bins + # convert 0.0 to 1.0 to 1 - 10 + color_index = math.floor(agent.risk_level * 10) + portrayal["Color"] = colors[color_index] + + # size based on wealth + # TODO: make this more of a gradient + if agent.wealth > agent.model.initial_wealth / 2: + portrayal["r"] = 0.2 + elif math.isclose(agent.wealth, agent.model.initial_wealth, abs_tol=100): + portrayal["r"] = 0.4 + else: + portrayal["r"] = 0.7 + return portrayal + + +grid_size = 10 + +grid = mesa.visualization.CanvasGrid(agent_portrayal, grid_size, grid_size, 500, 500) +chart = mesa.visualization.ChartModule( + # TODO: figure out what data points are worth collecting and reporting here + [ + # {"Label": "stag_hunters", "Color": "green"}, + # {"Label": "hare_hunters", "Color": "blue"}, + ], + # data_collector_name="datacollector", +) + +server = mesa.visualization.ModularServer( + RiskyBetModel, + [grid, chart], + "Risky Bet Simulation", + {"grid_size": grid_size}, +) +server.port = 8521 # The default +server.launch() From 25a841b33d88875b239d0a51ddf302c4899facc5 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 20 Jun 2023 17:32:23 -0400 Subject: [PATCH 020/141] Remove unused line of code --- simulatingrisk/risky_food/model.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/simulatingrisk/risky_food/model.py b/simulatingrisk/risky_food/model.py index 798972c..06bc926 100644 --- a/simulatingrisk/risky_food/model.py +++ b/simulatingrisk/risky_food/model.py @@ -59,8 +59,6 @@ def step(self): # pick a probability for risky food being not contaminated this round self.prob_notcontaminated = self.random.random() - self.prob_notcontaminated * 100 - self.risky_food_status = self.get_risky_food_status() self.schedule.step() From 49e5ddadeb2af25ad76b2019edd2b29d1b9243db Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 21 Jun 2023 09:56:13 -0400 Subject: [PATCH 021/141] Add data collection and chart for risk level quartiles --- simulatingrisk/risky_bet/model.py | 113 +++++++++++++++++++++++++----- simulatingrisk/risky_bet/run.py | 43 ++++++------ 2 files changed, 117 insertions(+), 39 deletions(-) diff --git a/simulatingrisk/risky_bet/model.py b/simulatingrisk/risky_bet/model.py index ffed8ce..6cedc9d 100644 --- a/simulatingrisk/risky_bet/model.py +++ b/simulatingrisk/risky_bet/model.py @@ -1,20 +1,44 @@ from enum import Enum +import statistics import mesa Bet = Enum("Bet", ["RISKY", "SAFE"]) bet_choices = [Bet.SAFE, Bet.RISKY] +# divergent color scheme, ten colors +# from https://colorbrewer2.org/#type=diverging&scheme=RdYlGn&n=10 +divergent_colors = [ + "#a50026", + "#d73027", + "#f46d43", + "#fdae61", + "#fee08b", + "#d9ef8b", + "#a6d96a", + "#66bd63", + "#1a9850", + "#006837", +] +divergent_colors.reverse() # reverse to get green first and red last + class RiskyGambler(mesa.Agent): def __init__(self, unique_id, model, initial_wealth): super().__init__(unique_id, model) # starting wealth determined by the model + self.initial_wealth = initial_wealth # store initial value self.wealth = initial_wealth # get a random risk tolerance; returns a value between 0.0 and 1.0 self.risk_level = self.random.random() self.choice = None + def __repr__(self): + return ( + f"" + ) + def step(self): # decide how to bet based on risk tolerance and likelihood # that the risky bet will pay off @@ -26,25 +50,41 @@ def step(self): # determine the payoff for this choice self.calculate_payoff(self.choice) + # every ten rounds, agents adjust their risk level + # make this a model method? + if self.model.adjustment_round(): + self.adjust_risk() + def calculate_payoff(self, choice): if choice == Bet.RISKY: # if the risky bet paid off, multiply current wealth by 1.5 if self.model.risky_payoff: self.wealth = self.wealth * 1.5 - print( - "agent %s risky bet paid off, wealth is now %s" - % (self.unique_id, self.wealth) - ) # if it doesn't, multiply by 0.5 else: self.wealth = self.wealth * 0.5 - print( - "agent %s risky bet did not paid off, wealth is now %s" - % (self.unique_id, self.wealth) - ) - # safe choice = wealth stays the same - print("agent %s no bet, wealth stays %s" % (self.unique_id, self.wealth)) + + # otherwise, no change + + def adjust_risk(self): + # look at neighbors (4) + # if anyone has more money, + # adopt their risk attitude + # [other possibilities: average between your risk attitude and theirs]. + # And then reset wealth back to initial value + + # get neighbors; use Von Neumann neighboard (no diagonals), don't include self + neighbors = self.model.grid.get_neighbors(self.pos, False, True) + # sort neighbors by wealth, wealthiest neighbor first + neighbors.sort(key=lambda x: x.wealth, reverse=True) + wealthiest = neighbors[0] + # if wealthiest neighbor is richer than me, adopt their risk level + if wealthiest.wealth > self.wealth: + self.risk_level = wealthiest.risk_level + + # reset wealth back to initial value + self.wealth = self.initial_wealth class RiskyBetModel(mesa.Model): @@ -66,14 +106,12 @@ def __init__(self, grid_size): self.grid.move_to_empty(a) self.datacollector = mesa.DataCollector( - # TODO: figure out what data to collect model_reporters={ - # "prob_notcontaminated": "prob_notcontaminated", - # "contaminated": "contaminated", - # "average_risk_level": "avg_risk_level", - # "min_risk_level": "min_risk_level", - # "max_risk_level": "max_risk_level", - # "num_agents": "total_agents", + "risk_min": "risk_min", + "risk_q1": "risk_q1", + "risk_mean": "risk_mean", + "risk_q3": "risk_q3", + "risk_max": "risk_max", }, agent_reporters={"risk_level": "risk_level", "choice": "choice"}, ) @@ -87,8 +125,8 @@ def step(self): self.risky_payoff = self.call_risky_bet() self.schedule.step() - # TODO: add periodic agent risk adjustment (every ten rounds) self.datacollector.collect(self) + # every ten rounds, agents adjust their risk level def call_risky_bet(self): # flip a weighted coin to determine if the risky bet pays off, @@ -97,3 +135,42 @@ def call_risky_bet(self): [True, False], weights=[self.prob_risky_payoff, 1 - self.prob_risky_payoff], )[0] + + def adjustment_round(self): + # agents should adjust their wealth every 10 rounds; + # check if the current step is an adjustment round + return self.schedule.steps > 0 and self.schedule.steps % 10 == 0 + + # TODO: possible to cache but invalidate when step changes? + @property + def agent_risk_levels(self): + return [a.risk_level for a in self.schedule.agent_buffer()] + + @property + def risk_median(self): + # calculate median of current agent risk levels + return statistics.median(self.agent_risk_levels) + + @property + def risk_mean(self): + return statistics.mean(self.agent_risk_levels) + + @property + def risk_min(self): + return min(self.agent_risk_levels) + + @property + def risk_max(self): + return max(self.agent_risk_levels) + + @property + def risk_q1(self): + risk_median = self.risk_median + # first quartile is the median of values less than the median + return statistics.median([r for r in self.agent_risk_levels if r < risk_median]) + + @property + def risk_q3(self): + risk_median = self.risk_median + # third quartile is the median of values greater than the median + return statistics.median([r for r in self.agent_risk_levels if r > risk_median]) diff --git a/simulatingrisk/risky_bet/run.py b/simulatingrisk/risky_bet/run.py index 7b5793d..ecfa5d8 100644 --- a/simulatingrisk/risky_bet/run.py +++ b/simulatingrisk/risky_bet/run.py @@ -1,25 +1,11 @@ import mesa -from simulatingrisk.risky_bet.model import RiskyBetModel +from simulatingrisk.risky_bet.model import RiskyBetModel, divergent_colors def agent_portrayal(agent): import math - - # divergent color scheme, ten colors - # from https://colorbrewer2.org/#type=diverging&scheme=RdYlGn&n=10 - colors = [ - "#a50026", - "#d73027", - "#f46d43", - "#fdae61", - "#fee08b", - "#d9ef8b", - "#a6d96a", - "#66bd63", - "#1a9850", - "#006837", - ] + from simulatingrisk.risky_bet.model import divergent_colors portrayal = { "Shape": "circle", @@ -32,7 +18,7 @@ def agent_portrayal(agent): # color based on risk level, with ten bins # convert 0.0 to 1.0 to 1 - 10 color_index = math.floor(agent.risk_level * 10) - portrayal["Color"] = colors[color_index] + portrayal["Color"] = divergent_colors[color_index] # size based on wealth # TODO: make this more of a gradient @@ -47,14 +33,29 @@ def agent_portrayal(agent): grid_size = 10 +colors = [ + "#a50026", + "#d73027", + "#f46d43", + "#fdae61", + "#fee08b", + "#d9ef8b", + "#a6d96a", + "#66bd63", + "#1a9850", + "#006837", +] + grid = mesa.visualization.CanvasGrid(agent_portrayal, grid_size, grid_size, 500, 500) chart = mesa.visualization.ChartModule( - # TODO: figure out what data points are worth collecting and reporting here [ - # {"Label": "stag_hunters", "Color": "green"}, - # {"Label": "hare_hunters", "Color": "blue"}, + {"Label": "risk_min", "Color": divergent_colors[0]}, + {"Label": "risk_q1", "Color": divergent_colors[3]}, + {"Label": "risk_mean", "Color": divergent_colors[5]}, + {"Label": "risk_q3", "Color": divergent_colors[7]}, + {"Label": "risk_max", "Color": divergent_colors[-1]}, ], - # data_collector_name="datacollector", + data_collector_name="datacollector", ) server = mesa.visualization.ModularServer( From 2c55c683efabaad1cbac814e1560d26d7e607da6 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 21 Jun 2023 10:07:20 -0400 Subject: [PATCH 022/141] Set size portrayal based on current wealth within wealth distribution --- simulatingrisk/risky_bet/model.py | 5 +++++ simulatingrisk/risky_bet/run.py | 22 +++++++++++++--------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/simulatingrisk/risky_bet/model.py b/simulatingrisk/risky_bet/model.py index 6cedc9d..b7468e7 100644 --- a/simulatingrisk/risky_bet/model.py +++ b/simulatingrisk/risky_bet/model.py @@ -146,6 +146,11 @@ def adjustment_round(self): def agent_risk_levels(self): return [a.risk_level for a in self.schedule.agent_buffer()] + @property + def max_agent_wealth(self): + # what is the current largest wealth of any agent? + return max([a.wealth for a in self.schedule.agent_buffer()]) + @property def risk_median(self): # calculate median of current agent risk levels diff --git a/simulatingrisk/risky_bet/run.py b/simulatingrisk/risky_bet/run.py index ecfa5d8..a8e2355 100644 --- a/simulatingrisk/risky_bet/run.py +++ b/simulatingrisk/risky_bet/run.py @@ -7,6 +7,7 @@ def agent_portrayal(agent): import math from simulatingrisk.risky_bet.model import divergent_colors + # initial display portrayal = { "Shape": "circle", "Color": "gray", @@ -20,18 +21,21 @@ def agent_portrayal(agent): color_index = math.floor(agent.risk_level * 10) portrayal["Color"] = divergent_colors[color_index] - # size based on wealth - # TODO: make this more of a gradient - if agent.wealth > agent.model.initial_wealth / 2: - portrayal["r"] = 0.2 - elif math.isclose(agent.wealth, agent.model.initial_wealth, abs_tol=100): - portrayal["r"] = 0.4 - else: - portrayal["r"] = 0.7 + # size based on wealth within current distribution + max_wealth = agent.model.max_agent_wealth + wealth_index = math.floor(agent.wealth / max_wealth * 10) + # set radius based on wealth, but don't go smaller than 0.1 radius + # or too large to fit in the grid + portrayal["r"] = wealth_index / 15 + 0.1 + + # TODO: change shape based on number of times risk level has been adjusted? + # can't find a list of available shapes; setting to triangle and square + # results in a 404 for a local custom url + return portrayal -grid_size = 10 +grid_size = 20 colors = [ "#a50026", From 1ea349ed764e8463ecb6aaeccfd163cfd94a2ffb Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 21 Jun 2023 10:37:29 -0400 Subject: [PATCH 023/141] Make risk adjustment strategy configurable --- simulatingrisk/risky_bet/model.py | 23 ++++++++++++++++++++--- simulatingrisk/risky_bet/run.py | 21 ++++++++++++++++++++- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/simulatingrisk/risky_bet/model.py b/simulatingrisk/risky_bet/model.py index b7468e7..c4acbb7 100644 --- a/simulatingrisk/risky_bet/model.py +++ b/simulatingrisk/risky_bet/model.py @@ -79,20 +79,37 @@ def adjust_risk(self): # sort neighbors by wealth, wealthiest neighbor first neighbors.sort(key=lambda x: x.wealth, reverse=True) wealthiest = neighbors[0] - # if wealthiest neighbor is richer than me, adopt their risk level + # if wealthiest neighbor is richer, adjust if wealthiest.wealth > self.wealth: - self.risk_level = wealthiest.risk_level + # adjust risk based on model configuration + if self.model.risk_adjustment == "adopt": + # adopt wealthiest neighbor's risk level + self.risk_level = wealthiest.risk_level + elif self.model.risk_adjustment == "average": + # average theirs with mine + self.risk_level = statistics.mean( + [self.risk_level, wealthiest.risk_level] + ) # reset wealth back to initial value self.wealth = self.initial_wealth class RiskyBetModel(mesa.Model): + """ + Model for simulating a risky bet game. + + :param grid_size: number for square grid size (creates n*n agents) + :param risk_adjustment: strategy agents should use for adjusting risk; + adopt (default), or average + """ + initial_wealth = 1000 - def __init__(self, grid_size): + def __init__(self, grid_size, risk_adjustment="adopt"): # assume a fully-populated square grid self.num_agents = grid_size * grid_size + self.risk_adjustment = risk_adjustment # initialize a single grid (each square inhabited by a single agent); # configure the grid to wrap around so everyone has neighbors self.grid = mesa.space.SingleGrid(grid_size, grid_size, True) diff --git a/simulatingrisk/risky_bet/run.py b/simulatingrisk/risky_bet/run.py index a8e2355..5879c02 100644 --- a/simulatingrisk/risky_bet/run.py +++ b/simulatingrisk/risky_bet/run.py @@ -50,6 +50,25 @@ def agent_portrayal(agent): "#006837", ] +# make model parameters user-configurable +model_params = { + "grid_size": grid_size, # mesa.visualization.StaticText(value=grid_size), + # "grid_size": mesa.visualization.Slider( + # "Grid size", + # value=20, + # min_value=10, + # max_value=100, + # description="Grid dimension (n*n = number of agents)", + # ), + "risk_adjustment": mesa.visualization.Choice( + "Risk adjustment strategy", + value="adopt", + choices=["adopt", "average"], + description="How agents update their risk level", + ), +} + + grid = mesa.visualization.CanvasGrid(agent_portrayal, grid_size, grid_size, 500, 500) chart = mesa.visualization.ChartModule( [ @@ -66,7 +85,7 @@ def agent_portrayal(agent): RiskyBetModel, [grid, chart], "Risky Bet Simulation", - {"grid_size": grid_size}, + model_params=model_params, ) server.port = 8521 # The default server.launch() From bb639220be51c63951482c078f76eb3fe03fa36c Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 21 Jun 2023 13:52:29 -0400 Subject: [PATCH 024/141] Add data collection & chart to track the risk and payoff --- simulatingrisk/risky_bet/model.py | 7 ++++++- simulatingrisk/risky_bet/run.py | 11 +++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/simulatingrisk/risky_bet/model.py b/simulatingrisk/risky_bet/model.py index c4acbb7..bf52b1d 100644 --- a/simulatingrisk/risky_bet/model.py +++ b/simulatingrisk/risky_bet/model.py @@ -124,6 +124,10 @@ def __init__(self, grid_size, risk_adjustment="adopt"): self.datacollector = mesa.DataCollector( model_reporters={ + # state of the world + "prob_risky_payoff": "prob_risky_payoff", + "risky_bet": "risky_bet", + # aggregate information about agents "risk_min": "risk_min", "risk_q1": "risk_q1", "risk_mean": "risk_mean", @@ -148,10 +152,11 @@ def step(self): def call_risky_bet(self): # flip a weighted coin to determine if the risky bet pays off, # weighted by current round payoff probability - return self.random.choices( + self.risky_bet = self.random.choices( [True, False], weights=[self.prob_risky_payoff, 1 - self.prob_risky_payoff], )[0] + return self.risky_bet def adjustment_round(self): # agents should adjust their wealth every 10 rounds; diff --git a/simulatingrisk/risky_bet/run.py b/simulatingrisk/risky_bet/run.py index 5879c02..ac9e644 100644 --- a/simulatingrisk/risky_bet/run.py +++ b/simulatingrisk/risky_bet/run.py @@ -70,7 +70,7 @@ def agent_portrayal(agent): grid = mesa.visualization.CanvasGrid(agent_portrayal, grid_size, grid_size, 500, 500) -chart = mesa.visualization.ChartModule( +risk_chart = mesa.visualization.ChartModule( [ {"Label": "risk_min", "Color": divergent_colors[0]}, {"Label": "risk_q1", "Color": divergent_colors[3]}, @@ -80,10 +80,17 @@ def agent_portrayal(agent): ], data_collector_name="datacollector", ) +world_chart = mesa.visualization.ChartModule( + [ + {"Label": "prob_risky_payoff", "Color": "gray"}, + {"Label": "risky_bet", "Color": "blue"}, + ], + data_collector_name="datacollector", +) server = mesa.visualization.ModularServer( RiskyBetModel, - [grid, chart], + [grid, risk_chart, world_chart], "Risky Bet Simulation", model_params=model_params, ) From 3640b77c6bf45a3ddb932cf04a8263af6ac385e0 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 21 Jun 2023 13:58:38 -0400 Subject: [PATCH 025/141] Make agent count more efficient --- simulatingrisk/risky_food/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simulatingrisk/risky_food/model.py b/simulatingrisk/risky_food/model.py index 06bc926..6027c29 100644 --- a/simulatingrisk/risky_food/model.py +++ b/simulatingrisk/risky_food/model.py @@ -110,7 +110,7 @@ def agents(self): @property def total_agents(self): - return len(list(self.agents)) + return self.schedule.get_agent_count() @property def avg_risk_level(self): From 6a5a594abd0b9bab520820356289fa00dfea9ef6 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 21 Jun 2023 16:55:31 -0400 Subject: [PATCH 026/141] Correct colors and risk taking logic based on meeting review --- simulatingrisk/risky_bet/model.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/simulatingrisk/risky_bet/model.py b/simulatingrisk/risky_bet/model.py index bf52b1d..e4eabb6 100644 --- a/simulatingrisk/risky_bet/model.py +++ b/simulatingrisk/risky_bet/model.py @@ -20,7 +20,8 @@ "#1a9850", "#006837", ] -divergent_colors.reverse() # reverse to get green first and red last +# low values = risk inclined (more likely to take a risky bet) +# higher value = risk averse (less likely to the bet) class RiskyGambler(mesa.Agent): @@ -42,7 +43,7 @@ def __repr__(self): def step(self): # decide how to bet based on risk tolerance and likelihood # that the risky bet will pay off - if self.risk_level > self.model.prob_risky_payoff: + if self.model.prob_risky_payoff > self.risk_level: self.choice = Bet.RISKY else: self.choice = Bet.SAFE @@ -75,7 +76,7 @@ def adjust_risk(self): # And then reset wealth back to initial value # get neighbors; use Von Neumann neighboard (no diagonals), don't include self - neighbors = self.model.grid.get_neighbors(self.pos, False, True) + neighbors = self.model.grid.get_neighbors(self.pos, False, False) # sort neighbors by wealth, wealthiest neighbor first neighbors.sort(key=lambda x: x.wealth, reverse=True) wealthiest = neighbors[0] From 99738dee4087fb8223840bdcabf619c333c891a1 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 18 Jul 2023 11:18:45 -0400 Subject: [PATCH 027/141] Refactor weighted choice into a resuable weighted coin flip method --- simulatingrisk/risky_food/model.py | 12 ++++--- simulatingrisk/utils.py | 22 +++++++++++++ tests/test_risky_food.py | 3 ++ tests/test_utils.py | 52 ++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 simulatingrisk/utils.py create mode 100644 tests/test_utils.py diff --git a/simulatingrisk/risky_food/model.py b/simulatingrisk/risky_food/model.py index 798972c..f89036d 100644 --- a/simulatingrisk/risky_food/model.py +++ b/simulatingrisk/risky_food/model.py @@ -3,6 +3,8 @@ import mesa +from simulatingrisk.utils import coinflip + class FoodChoice(Enum): RISKY = "R" @@ -73,12 +75,12 @@ def get_risky_food_status(self): # determine actual food status for this round, # weighted by probability of non-contamination - # randomly choose, with choice weighted by + # randomly choose status, with first choice weighted by # current probability not contaminated - return self.random.choices( - [FoodStatus.NOTCONTAMINATED, FoodStatus.CONTAMINATED], - weights=[self.prob_notcontaminated, 1 - self.prob_notcontaminated], - )[0] + return coinflip( + choices=[FoodStatus.NOTCONTAMINATED, FoodStatus.CONTAMINATED], + weight=self.prob_notcontaminated, + ) def propagate(self): # update agents based on payoff from the completed round diff --git a/simulatingrisk/utils.py b/simulatingrisk/utils.py new file mode 100644 index 0000000..24c80f4 --- /dev/null +++ b/simulatingrisk/utils.py @@ -0,0 +1,22 @@ +import random + + +def coinflip(choices: [any, any] = [0, 1], weight: float = None) -> any: + """Flip a coin with an optional weight between 0.0 and 1.0 for the + first choice. If no weight is specified, a choice is made with + equal probability. + + :param choices: list of coin flip options, defaults to [0, 1] + :type choices: [any, any] (optional) + :param weight: optional weight between 0.0-1.0 for the first + choice, defaults to None + :type weight: float (optional) + + :return: selected choice + :rtype: any + """ + options = {} + if weight: + options["weights"] = [weight, 1 - weight] + # random.choices sorts options based on weight; return first choice + return random.choices(choices, **options)[0] diff --git a/tests/test_risky_food.py b/tests/test_risky_food.py index c5e7c49..d65eab0 100644 --- a/tests/test_risky_food.py +++ b/tests/test_risky_food.py @@ -17,6 +17,9 @@ def test_risky_food_status(prob_notcontaminated): # test that food status choice is weighted properly # by probability of not being contaminated + # NOTE: this is redundant now that we've refactored the logic + # into a reusable coin flip method; keeping to confirm refactor works + # initialize model with one agent model = RiskyFoodModel(1) model.prob_notcontaminated = prob_notcontaminated diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..be340ba --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,52 @@ +from collections import Counter +import math +import pytest + +from simulatingrisk.utils import coinflip + + +test_probabilities = [ + (None), + (0.5), + (0.2), + (0.8), +] + + +@pytest.mark.parametrize("weight", test_probabilities) +def test_coinflip_weights(weight): + # test that coin flip method weight logic is working properly + + # run 100 times so we can check the percentage of + # choices made is close to expected weight + results = [] + total_runs = 100 + for i in range(total_runs): + results.append(coinflip(weight=weight)) + + # use counter to tally the results + result_count = Counter(results) + + # the expected value is the probability times number of times we ran it + # - unspecified weight should be equivalent to 0.5 + if weight is None: + weight = 0.5 + expected = total_runs * weight + assert math.isclose(result_count[0], expected, abs_tol=total_runs * 0.1) + + +def test_coinflip_choices(): + # test using non-default choices + choices = ["a", "b"] + results = [] + total_runs = 10 + for i in range(total_runs): + results.append(coinflip(choices=choices)) + + # use counter to tally the results + result_count = Counter(results) + # we should only have choices within our specified set + assert set(result_count.keys()) == set(choices) + # we expect an equal distribution of those choices + for choice in choices: + assert math.isclose(result_count[choice], 5, abs_tol=total_runs * 0.5) From edf3287784411842b8969ed699ef5c541bcbdfba Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 18 Jul 2023 12:04:04 -0400 Subject: [PATCH 028/141] Start adding unit tests for risky bed model --- simulatingrisk/risky_bet/model.py | 41 +++++++++++++++--------- tests/test_risky_bet.py | 53 +++++++++++++++++++++++++++++++ tests/test_utils.py | 2 +- 3 files changed, 80 insertions(+), 16 deletions(-) create mode 100644 tests/test_risky_bet.py diff --git a/simulatingrisk/risky_bet/model.py b/simulatingrisk/risky_bet/model.py index e4eabb6..51b6968 100644 --- a/simulatingrisk/risky_bet/model.py +++ b/simulatingrisk/risky_bet/model.py @@ -1,19 +1,24 @@ from enum import Enum +from functools import cached_property import statistics import mesa +from simulatingrisk.utils import coinflip + + Bet = Enum("Bet", ["RISKY", "SAFE"]) bet_choices = [Bet.SAFE, Bet.RISKY] -# divergent color scheme, ten colors -# from https://colorbrewer2.org/#type=diverging&scheme=RdYlGn&n=10 +# divergent color scheme, eleven colors +# from https://colorbrewer2.org/?type=diverging&scheme=RdYlGn&n=11 divergent_colors = [ "#a50026", "#d73027", "#f46d43", "#fdae61", "#fee08b", + "#ffffbf", "#d9ef8b", "#a6d96a", "#66bd63", @@ -53,7 +58,7 @@ def step(self): # every ten rounds, agents adjust their risk level # make this a model method? - if self.model.adjustment_round(): + if self.model.adjustment_round: self.adjust_risk() def calculate_payoff(self, choice): @@ -68,18 +73,25 @@ def calculate_payoff(self, choice): # otherwise, no change + @cached_property + def neighbors(self): + """Get neighbors for the current agent; uses Von Neumann + neighborhood (no diagonals), does not include self.""" + # because this simulation doesn't include any movement, + # neighbors won't change over the run and we can cache + return self.model.grid.get_neighbors( + self.pos, moore=False, include_center=False + ) + def adjust_risk(self): # look at neighbors (4) # if anyone has more money, - # adopt their risk attitude - # [other possibilities: average between your risk attitude and theirs]. - # And then reset wealth back to initial value + # either adopt their risk attitude or average theirs with yours + # then reset wealth back to initial value - # get neighbors; use Von Neumann neighboard (no diagonals), don't include self - neighbors = self.model.grid.get_neighbors(self.pos, False, False) # sort neighbors by wealth, wealthiest neighbor first - neighbors.sort(key=lambda x: x.wealth, reverse=True) - wealthiest = neighbors[0] + self.neighbors.sort(key=lambda x: x.wealth, reverse=True) + wealthiest = self.neighbors[0] # if wealthiest neighbor is richer, adjust if wealthiest.wealth > self.wealth: # adjust risk based on model configuration @@ -153,13 +165,12 @@ def step(self): def call_risky_bet(self): # flip a weighted coin to determine if the risky bet pays off, # weighted by current round payoff probability - self.risky_bet = self.random.choices( - [True, False], - weights=[self.prob_risky_payoff, 1 - self.prob_risky_payoff], - )[0] + self.risky_bet = coinflip([True, False], weight=self.prob_risky_payoff) return self.risky_bet - def adjustment_round(self): + @property + def adjustment_round(self) -> bool: + """is the current round an adjustment round?""" # agents should adjust their wealth every 10 rounds; # check if the current step is an adjustment round return self.schedule.steps > 0 and self.schedule.steps % 10 == 0 diff --git a/tests/test_risky_bet.py b/tests/test_risky_bet.py new file mode 100644 index 0000000..fabaed0 --- /dev/null +++ b/tests/test_risky_bet.py @@ -0,0 +1,53 @@ +from collections import Counter +import math + +from simulatingrisk.risky_bet.model import RiskyBetModel + + +def test_call_risky_bet(): + # test risk bet generating payoff probability and calling the bet + + # initialize model with a few agents (3x3 grid) + model = RiskyBetModel(3) + + probabilities = [] + results = [] + total_runs = 10 + for i in range(total_runs): + model.step() + probabilities.append(model.prob_risky_payoff) + results.append(model.risky_payoff) + + # use counter to tally the results + result_count = Counter(results) + + # confirm probabilities are in expected range + assert all([(p > 0.0 and p < 1.0) for p in probabilities]) + # not sure how to reliably test the payoff... + # payoff results depend on the probabilities, + # but should be some even-ish split + assert math.isclose(result_count[True], total_runs / 2, abs_tol=3) + + +def test_adjustment_round(): + model = RiskyBetModel(3) + + for metaround in range(3): + # run for nine rounds, none of them should be adjustment rounds + for i in range(9): + model.step() + assert model.adjustment_round is False + # tenth round is an adjustment round + model.step() + assert model.adjustment_round + + +def test_riskygambler_neighbors(): + # initialize model with a few agents (3x3 grid) + model = RiskyBetModel(4) + model.step() + + # every agent should have 4 neighbors, + # even if they are on the edge of the grid + for agent in model.schedule.agent_buffer(): + assert len(agent.neighbors) == 4 diff --git a/tests/test_utils.py b/tests/test_utils.py index be340ba..4469da3 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -32,7 +32,7 @@ def test_coinflip_weights(weight): if weight is None: weight = 0.5 expected = total_runs * weight - assert math.isclose(result_count[0], expected, abs_tol=total_runs * 0.1) + assert math.isclose(result_count[0], expected, abs_tol=total_runs * 0.2) def test_coinflip_choices(): From 99237d14092ad0a7eb30a0b1699aa95f438ab394 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 18 Jul 2023 12:37:03 -0400 Subject: [PATCH 029/141] Add more unit tests for risky bet model and agent --- simulatingrisk/risky_bet/model.py | 26 ++++++++--- tests/test_risky_bet.py | 77 +++++++++++++++++++++++++++---- 2 files changed, 89 insertions(+), 14 deletions(-) diff --git a/simulatingrisk/risky_bet/model.py b/simulatingrisk/risky_bet/model.py index 51b6968..9f2d49e 100644 --- a/simulatingrisk/risky_bet/model.py +++ b/simulatingrisk/risky_bet/model.py @@ -83,15 +83,19 @@ def neighbors(self): self.pos, moore=False, include_center=False ) + @property + def wealthiest_neighbor(self): + """identify and return the current wealthiest neighbor""" + # sort neighbors by wealth, wealthiest neighbor first + return sorted(self.neighbors, key=lambda x: x.wealth, reverse=True)[0] + def adjust_risk(self): # look at neighbors (4) # if anyone has more money, # either adopt their risk attitude or average theirs with yours # then reset wealth back to initial value - # sort neighbors by wealth, wealthiest neighbor first - self.neighbors.sort(key=lambda x: x.wealth, reverse=True) - wealthiest = self.neighbors[0] + wealthiest = self.wealthiest_neighbor # if wealthiest neighbor is richer, adjust if wealthiest.wealth > self.wealth: # adjust risk based on model configuration @@ -162,6 +166,9 @@ def step(self): self.datacollector.collect(self) # every ten rounds, agents adjust their risk level + # delete cached property before the next round + del self.agent_risk_levels + def call_risky_bet(self): # flip a weighted coin to determine if the risky bet pays off, # weighted by current round payoff probability @@ -175,9 +182,12 @@ def adjustment_round(self) -> bool: # check if the current step is an adjustment round return self.schedule.steps > 0 and self.schedule.steps % 10 == 0 - # TODO: possible to cache but invalidate when step changes? - @property - def agent_risk_levels(self): + @cached_property + def agent_risk_levels(self) -> [float]: + # list of all risk levels for all current agents; + # property is cached but should be cleared in each new round + + # NOTE: occasionally median method is complaining that this is empty return [a.risk_level for a in self.schedule.agent_buffer()] @property @@ -188,6 +198,10 @@ def max_agent_wealth(self): @property def risk_median(self): # calculate median of current agent risk levels + if not self.agent_risk_levels: + # occasionally this complains about an empty list + # hopefully only possible in unit tests... + return return statistics.median(self.agent_risk_levels) @property diff --git a/tests/test_risky_bet.py b/tests/test_risky_bet.py index fabaed0..92a7cfe 100644 --- a/tests/test_risky_bet.py +++ b/tests/test_risky_bet.py @@ -1,7 +1,9 @@ from collections import Counter import math +from unittest.mock import Mock, patch +import statistics -from simulatingrisk.risky_bet.model import RiskyBetModel +from simulatingrisk.risky_bet.model import RiskyBetModel, RiskyGambler def test_call_risky_bet(): @@ -32,14 +34,16 @@ def test_call_risky_bet(): def test_adjustment_round(): model = RiskyBetModel(3) - for metaround in range(3): - # run for nine rounds, none of them should be adjustment rounds - for i in range(9): - model.step() - assert model.adjustment_round is False - # tenth round is an adjustment round + # run for nine rounds, none of them should be adjustment rounds + for i in range(9): model.step() - assert model.adjustment_round + assert not model.adjustment_round + # tenth round is an adjustment round + model.step() + assert model.adjustment_round + # next round is not + model.step() + assert not model.adjustment_round def test_riskygambler_neighbors(): @@ -51,3 +55,60 @@ def test_riskygambler_neighbors(): # even if they are on the edge of the grid for agent in model.schedule.agent_buffer(): assert len(agent.neighbors) == 4 + + +def test_riskygambler_wealthiestneighbor(): + # initialize an agent with a mock model + agent = RiskyGambler(1, Mock(), 1000) + mock_neighbors = [ + Mock(wealth=1), + Mock(wealth=45), + Mock(wealth=232), + Mock(wealth=32), + ] + + with patch.object(RiskyGambler, "neighbors", mock_neighbors): + assert agent.wealthiest_neighbor.wealth == 232 + + +def test_riskygambler_adjust_risk_adopt(): + # initialize an agent with a mock model + agent = RiskyGambler(1, Mock(risk_adjustment="adopt"), 1000) + # set a known risk level + agent.risk_level = 0.3 + # adjust wealth as if the model had run + agent.wealth = 300 + # set a mock wealthiest neighbor with more wealth than current agent + neighbor = Mock(wealth=1500, risk_level=0.2) + with patch.object(RiskyGambler, "wealthiest_neighbor", neighbor): + agent.adjust_risk() + # default behavior is to adopt successful risk level + assert agent.risk_level == neighbor.risk_level + # wealth should reset to initial value + assert agent.wealth == agent.initial_wealth + + # now simulate a wealthiest neighbor with less wealth than current agent + neighbor.wealth = 240 + neighbor.risk_level = 0.4 + prev_risk_level = agent.risk_level + agent.adjust_risk() + # risk level should not be changed + assert agent.risk_level == prev_risk_level + + +def test_riskygambler_adjust_risk_average(): + # same as previous test, but with average risk adjustment strategy + agent = RiskyGambler(1, Mock(risk_adjustment="average"), 1000) + # set a known risk level + agent.risk_level = 0.7 + # adjust wealth as if the model had run + agent.wealth = 300 + # set a mock wealthiest neighbor with more wealth than current agent + neighbor = Mock(wealth=1500, risk_level=0.2) + with patch.object(RiskyGambler, "wealthiest_neighbor", neighbor): + prev_risk_level = agent.risk_level + agent.adjust_risk() + # new risk level should be average of previous and most successful + assert agent.risk_level == statistics.mean( + [neighbor.risk_level, prev_risk_level] + ) From d292fe37012166396a08be70b355e02a5504beba Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 18 Jul 2023 12:59:21 -0400 Subject: [PATCH 030/141] Use more efficient random method for weighted coin flip --- simulatingrisk/utils.py | 15 +++++++-------- tests/test_utils.py | 6 +++++- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/simulatingrisk/utils.py b/simulatingrisk/utils.py index 24c80f4..9c9406b 100644 --- a/simulatingrisk/utils.py +++ b/simulatingrisk/utils.py @@ -1,7 +1,7 @@ import random -def coinflip(choices: [any, any] = [0, 1], weight: float = None) -> any: +def coinflip(choices: [any, any] = [0, 1], weight: float = 0.5) -> any: """Flip a coin with an optional weight between 0.0 and 1.0 for the first choice. If no weight is specified, a choice is made with equal probability. @@ -9,14 +9,13 @@ def coinflip(choices: [any, any] = [0, 1], weight: float = None) -> any: :param choices: list of coin flip options, defaults to [0, 1] :type choices: [any, any] (optional) :param weight: optional weight between 0.0-1.0 for the first - choice, defaults to None - :type weight: float (optional) + choice, defaults to 0.5 + :type weight: float (toptional) :return: selected choice :rtype: any """ - options = {} - if weight: - options["weights"] = [weight, 1 - weight] - # random.choices sorts options based on weight; return first choice - return random.choices(choices, **options)[0] + # adapted from https://stackoverflow.com/a/477248/9706217 + # random.random is apparently faster than + selection = 0 if random.random() < weight else 1 + return choices[selection] diff --git a/tests/test_utils.py b/tests/test_utils.py index 4469da3..671d487 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -22,7 +22,11 @@ def test_coinflip_weights(weight): results = [] total_runs = 100 for i in range(total_runs): - results.append(coinflip(weight=weight)) + if weight: + results.append(coinflip(weight=weight)) + else: + # for None, don't specify the weight + results.append(coinflip()) # use counter to tally the results result_count = Counter(results) From 4c1dc0e8dba5b083c45f492d6ab4b303151e804c Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 18 Jul 2023 13:02:42 -0400 Subject: [PATCH 031/141] Rename agent to simplify and reduce redundancy --- simulatingrisk/risky_bet/model.py | 6 +++--- tests/test_risky_bet.py | 22 +++++++++++----------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/simulatingrisk/risky_bet/model.py b/simulatingrisk/risky_bet/model.py index 9f2d49e..8f54d81 100644 --- a/simulatingrisk/risky_bet/model.py +++ b/simulatingrisk/risky_bet/model.py @@ -29,7 +29,7 @@ # higher value = risk averse (less likely to the bet) -class RiskyGambler(mesa.Agent): +class Gambler(mesa.Agent): def __init__(self, unique_id, model, initial_wealth): super().__init__(unique_id, model) # starting wealth determined by the model @@ -41,7 +41,7 @@ def __init__(self, unique_id, model, initial_wealth): def __repr__(self): return ( - f"" ) @@ -134,7 +134,7 @@ def __init__(self, grid_size, risk_adjustment="adopt"): # initialize agents and add to grid and scheduler for i in range(self.num_agents): - a = RiskyGambler(i, self, self.initial_wealth) + a = Gambler(i, self, self.initial_wealth) self.schedule.add(a) # place randomly in an empty spot self.grid.move_to_empty(a) diff --git a/tests/test_risky_bet.py b/tests/test_risky_bet.py index 92a7cfe..891e539 100644 --- a/tests/test_risky_bet.py +++ b/tests/test_risky_bet.py @@ -3,7 +3,7 @@ from unittest.mock import Mock, patch import statistics -from simulatingrisk.risky_bet.model import RiskyBetModel, RiskyGambler +from simulatingrisk.risky_bet.model import RiskyBetModel, Gambler def test_call_risky_bet(): @@ -46,7 +46,7 @@ def test_adjustment_round(): assert not model.adjustment_round -def test_riskygambler_neighbors(): +def test_gambler_neighbors(): # initialize model with a few agents (3x3 grid) model = RiskyBetModel(4) model.step() @@ -57,9 +57,9 @@ def test_riskygambler_neighbors(): assert len(agent.neighbors) == 4 -def test_riskygambler_wealthiestneighbor(): +def test_gambler_wealthiestneighbor(): # initialize an agent with a mock model - agent = RiskyGambler(1, Mock(), 1000) + agent = Gambler(1, Mock(), 1000) mock_neighbors = [ Mock(wealth=1), Mock(wealth=45), @@ -67,20 +67,20 @@ def test_riskygambler_wealthiestneighbor(): Mock(wealth=32), ] - with patch.object(RiskyGambler, "neighbors", mock_neighbors): + with patch.object(Gambler, "neighbors", mock_neighbors): assert agent.wealthiest_neighbor.wealth == 232 -def test_riskygambler_adjust_risk_adopt(): +def test_gambler_adjust_risk_adopt(): # initialize an agent with a mock model - agent = RiskyGambler(1, Mock(risk_adjustment="adopt"), 1000) + agent = Gambler(1, Mock(risk_adjustment="adopt"), 1000) # set a known risk level agent.risk_level = 0.3 # adjust wealth as if the model had run agent.wealth = 300 # set a mock wealthiest neighbor with more wealth than current agent neighbor = Mock(wealth=1500, risk_level=0.2) - with patch.object(RiskyGambler, "wealthiest_neighbor", neighbor): + with patch.object(Gambler, "wealthiest_neighbor", neighbor): agent.adjust_risk() # default behavior is to adopt successful risk level assert agent.risk_level == neighbor.risk_level @@ -96,16 +96,16 @@ def test_riskygambler_adjust_risk_adopt(): assert agent.risk_level == prev_risk_level -def test_riskygambler_adjust_risk_average(): +def test_gambler_adjust_risk_average(): # same as previous test, but with average risk adjustment strategy - agent = RiskyGambler(1, Mock(risk_adjustment="average"), 1000) + agent = Gambler(1, Mock(risk_adjustment="average"), 1000) # set a known risk level agent.risk_level = 0.7 # adjust wealth as if the model had run agent.wealth = 300 # set a mock wealthiest neighbor with more wealth than current agent neighbor = Mock(wealth=1500, risk_level=0.2) - with patch.object(RiskyGambler, "wealthiest_neighbor", neighbor): + with patch.object(Gambler, "wealthiest_neighbor", neighbor): prev_risk_level = agent.risk_level agent.adjust_risk() # new risk level should be average of previous and most successful From b0f9fda3c9c20751d4331d635b5e12127b536dc7 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 18 Jul 2023 16:49:00 -0400 Subject: [PATCH 032/141] Adjust risk indexing to use 11 bins, including 0-0.5 and 0.95-1 --- simulatingrisk/risky_bet/run.py | 49 ++---------------------------- simulatingrisk/risky_bet/server.py | 47 ++++++++++++++++++++++++++++ tests/test_risky_bet.py | 17 +++++++++++ 3 files changed, 66 insertions(+), 47 deletions(-) create mode 100644 simulatingrisk/risky_bet/server.py diff --git a/simulatingrisk/risky_bet/run.py b/simulatingrisk/risky_bet/run.py index ac9e644..c92e2a7 100644 --- a/simulatingrisk/risky_bet/run.py +++ b/simulatingrisk/risky_bet/run.py @@ -1,54 +1,10 @@ import mesa from simulatingrisk.risky_bet.model import RiskyBetModel, divergent_colors - - -def agent_portrayal(agent): - import math - from simulatingrisk.risky_bet.model import divergent_colors - - # initial display - portrayal = { - "Shape": "circle", - "Color": "gray", - "Filled": "true", - "Layer": 0, - "r": 0.5, - } - - # color based on risk level, with ten bins - # convert 0.0 to 1.0 to 1 - 10 - color_index = math.floor(agent.risk_level * 10) - portrayal["Color"] = divergent_colors[color_index] - - # size based on wealth within current distribution - max_wealth = agent.model.max_agent_wealth - wealth_index = math.floor(agent.wealth / max_wealth * 10) - # set radius based on wealth, but don't go smaller than 0.1 radius - # or too large to fit in the grid - portrayal["r"] = wealth_index / 15 + 0.1 - - # TODO: change shape based on number of times risk level has been adjusted? - # can't find a list of available shapes; setting to triangle and square - # results in a 404 for a local custom url - - return portrayal - +from simulatingrisk.risky_bet.server import agent_portrayal grid_size = 20 -colors = [ - "#a50026", - "#d73027", - "#f46d43", - "#fdae61", - "#fee08b", - "#d9ef8b", - "#a6d96a", - "#66bd63", - "#1a9850", - "#006837", -] # make model parameters user-configurable model_params = { @@ -68,8 +24,8 @@ def agent_portrayal(agent): ), } - grid = mesa.visualization.CanvasGrid(agent_portrayal, grid_size, grid_size, 500, 500) + risk_chart = mesa.visualization.ChartModule( [ {"Label": "risk_min", "Color": divergent_colors[0]}, @@ -87,7 +43,6 @@ def agent_portrayal(agent): ], data_collector_name="datacollector", ) - server = mesa.visualization.ModularServer( RiskyBetModel, [grid, risk_chart, world_chart], diff --git a/simulatingrisk/risky_bet/server.py b/simulatingrisk/risky_bet/server.py new file mode 100644 index 0000000..b7919ae --- /dev/null +++ b/simulatingrisk/risky_bet/server.py @@ -0,0 +1,47 @@ +def risk_index(risk_level): + # risk levels range from 0.0 to 1.0, + # but we want eleven bins, with bins for 0 - 0.05 and 0.95 - 1.0, + # since risk = 0, 0.5, and 1 are all special cases we want clearly captured. + + # if we think of it as a range from -0.05 to 1.05, + # then we can work with evenly sized 0.1 bins + minval = -0.05 + binwidth = 0.1 + nbins = 11 + + # Determine which bin this element belongs in + binnum = int((risk_level - minval) // binwidth) # // = floor division + # convert bin number to 0-based index + return min(nbins - 1, binnum) + + +def agent_portrayal(agent): + import math + from simulatingrisk.risky_bet.model import divergent_colors + + # initial display + portrayal = { + "Shape": "circle", + "Color": "gray", + "Filled": "true", + "Layer": 0, + "r": 0.5, + } + + # color based on risk level, with ten bins + # convert 0.0 to 1.0 to 1 - 10 + color_index = math.floor(agent.risk_level * 10) + portrayal["Color"] = divergent_colors[color_index] + + # size based on wealth within current distribution + max_wealth = agent.model.max_agent_wealth + wealth_index = math.floor(agent.wealth / max_wealth * 10) + # set radius based on wealth, but don't go smaller than 0.1 radius + # or too large to fit in the grid + portrayal["r"] = wealth_index / 15 + 0.1 + + # TODO: change shape based on number of times risk level has been adjusted? + # can't find a list of available shapes; setting to triangle and square + # results in a 404 for a local custom url + + return portrayal diff --git a/tests/test_risky_bet.py b/tests/test_risky_bet.py index 891e539..907f27d 100644 --- a/tests/test_risky_bet.py +++ b/tests/test_risky_bet.py @@ -3,7 +3,10 @@ from unittest.mock import Mock, patch import statistics +import pytest + from simulatingrisk.risky_bet.model import RiskyBetModel, Gambler +from simulatingrisk.risky_bet.server import risk_index def test_call_risky_bet(): @@ -112,3 +115,17 @@ def test_gambler_adjust_risk_average(): assert agent.risk_level == statistics.mean( [neighbor.risk_level, prev_risk_level] ) + + +test_risk_index_bins = [ + (0.04, 0), # first bin is 0 - 0.05 + (0.09, 1), # 2nd : 0.05 - 0.15 + (0.18, 2), # 3nd : 0.15 - 0.25 + (0.32, 3), # 3nd : 0.25 - 0.35 + (0.98, 10), # last bin is 0.95 - 1 +] + + +@pytest.mark.parametrize("risk_level,expected_bin", test_risk_index_bins) +def test_risk_index(risk_level, expected_bin): + assert risk_index(risk_level) == expected_bin From 396cd133cd23e5f440fe05675204fbfda80a062b Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 18 Jul 2023 17:02:55 -0400 Subject: [PATCH 033/141] Add a readme to document the risky bet simulation --- simulatingrisk/risky_bet/README.md | 37 ++++++++++++++++++++++++++++++ simulatingrisk/risky_bet/server.py | 12 ++++++---- 2 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 simulatingrisk/risky_bet/README.md diff --git a/simulatingrisk/risky_bet/README.md b/simulatingrisk/risky_bet/README.md new file mode 100644 index 0000000..d873e73 --- /dev/null +++ b/simulatingrisk/risky_bet/README.md @@ -0,0 +1,37 @@ +# Risky Bet Simulation + +## Summary + +Game: agents start with random fixed risk attitudes (similar to the +risky food simulation), and decide whether or not to make a risky bet. +Every ten rounds, agents adjust their risk attitude based on the +relative wealth of their neighbors. + +SETUP: +- Fixed agents on an NxN grid with random risk attitudes +- Every agent starts with $1000 initial wealth + +EACH ROUND: +- The model selects a probability `p` of the RISKY bet paying off +- Each agent has a risk attitude `r`, and will take the RISKY bet if `p` > `r` +- The model flips a coin with bias `p` to determine whether the RISKY bet paid off. +- For agents who choose the SAFE option (no bet), money is unchanged; for agents who took the RISKY bet, money is either multiplied by 1.5 (if the bet paid off) or 0.5 (if it didn't). +END ROUND + +EVERY 10 ROUNDS, adjust risk attitudes: +- Each agent looks at their neighbors (4). +- If anyone has more money, either adopt their risk attitude or average between current risk attitude and theirs (configurable via a model intialization parameter). +- Reset wealth back to the initial value ($1000). + +Collect data to track how the distribution of risk attitudes changes over time. +Visualize a grid using a divergent color spectrum with for risk levels; use +eleven bins, with bins for 0 - 0.05 and 0.95 - 1.0, since risk = 0, 0.5, and 1 +are all special cases we want clearly captured. + +## Running the simulation + +- Install python dependencies as described in the main project readme (requires mesa) +- To run from the main `simulating-risk` project directory: + - Configure python to include the current directory in import path; + for C-based shells, run `setenv PYTHONPATH .` ; for bash, run `export $PYTHONPATH=.` + - To run interactively with mesa runserver: `mesa runserver simulatingrisk/risky_bet/` diff --git a/simulatingrisk/risky_bet/server.py b/simulatingrisk/risky_bet/server.py index b7919ae..1dd8863 100644 --- a/simulatingrisk/risky_bet/server.py +++ b/simulatingrisk/risky_bet/server.py @@ -1,15 +1,17 @@ def risk_index(risk_level): - # risk levels range from 0.0 to 1.0, - # but we want eleven bins, with bins for 0 - 0.05 and 0.95 - 1.0, - # since risk = 0, 0.5, and 1 are all special cases we want clearly captured. + """Calculate a risk bin index for a given risk level. + Risk levels range from 0.0 to 1.0, + Implement eleven bins, with bins for 0 - 0.05 and 0.95 - 1.0, + since risk = 0, 0.5, and 1 are all special cases we want clearly captured. + """ + # implementation adapted from https://stackoverflow.com/a/64995801/9706217 # if we think of it as a range from -0.05 to 1.05, # then we can work with evenly sized 0.1 bins minval = -0.05 binwidth = 0.1 nbins = 11 - - # Determine which bin this element belongs in + # Determine which bin this risk level belongs in binnum = int((risk_level - minval) // binwidth) # // = floor division # convert bin number to 0-based index return min(nbins - 1, binnum) From d85cb42d69deaea6b01da70a3e625e28a3437499 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 18 Jul 2023 18:36:28 -0400 Subject: [PATCH 034/141] Fix typo --- simulatingrisk/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simulatingrisk/utils.py b/simulatingrisk/utils.py index 9c9406b..a572b8f 100644 --- a/simulatingrisk/utils.py +++ b/simulatingrisk/utils.py @@ -10,7 +10,7 @@ def coinflip(choices: [any, any] = [0, 1], weight: float = 0.5) -> any: :type choices: [any, any] (optional) :param weight: optional weight between 0.0-1.0 for the first choice, defaults to 0.5 - :type weight: float (toptional) + :type weight: float (optional) :return: selected choice :rtype: any From 7245b34603ef7f6b434b119a6e0c55661ab4ad46 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 18 Jul 2023 17:56:29 -0400 Subject: [PATCH 035/141] Preliminary batch run script #7 --- simulatingrisk/batch_run.py | 38 +++++++++++++++++++++++++++++++ simulatingrisk/risky_bet/model.py | 1 + 2 files changed, 39 insertions(+) create mode 100644 simulatingrisk/batch_run.py diff --git a/simulatingrisk/batch_run.py b/simulatingrisk/batch_run.py new file mode 100644 index 0000000..22b75b1 --- /dev/null +++ b/simulatingrisk/batch_run.py @@ -0,0 +1,38 @@ +import csv +from datetime import datetime + +from mesa import batch_run + +from simulatingrisk.risky_bet.model import RiskyBetModel + + +def riskybet_batch_run(): + results = batch_run( + RiskyBetModel, + parameters={ + "grid_size": [10, 20, 30], # 100], + "risk_adjustment": ["adopt", "average"], + }, + iterations=5, + max_steps=100, + number_processes=1, # set None to use all available; set 1 for jypeter + data_collection_period=1, + display_progress=True, + ) + # returns a list of dictionaries that can be opened with pandas; + # save as csv for external analysis + # - use datetime to distinguish this run, but make nicer for filename + datestr = datetime.today().isoformat().replace(".", "_").replace(":", "") + output_filename = "riskybet_%s.csv" % datestr + print("Saving data collection results to: %s" % output_filename) + # get field names from last entry, since first entry is for the model + # and doesn't include agent-level data + fields = results[-1].keys() + with open(output_filename, "w", newline="") as output_file: + dict_writer = csv.DictWriter(output_file, fields) + dict_writer.writeheader() + dict_writer.writerows(results) + + +if __name__ == "__main__": + riskybet_batch_run() diff --git a/simulatingrisk/risky_bet/model.py b/simulatingrisk/risky_bet/model.py index 8f54d81..710f8f9 100644 --- a/simulatingrisk/risky_bet/model.py +++ b/simulatingrisk/risky_bet/model.py @@ -122,6 +122,7 @@ class RiskyBetModel(mesa.Model): """ initial_wealth = 1000 + running = True # required for batch run def __init__(self, grid_size, risk_adjustment="adopt"): # assume a fully-populated square grid From c65e638c387b57e27758da28df0979ae0f0e3cea Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 18 Jul 2023 18:21:14 -0400 Subject: [PATCH 036/141] Notebook with preliminary analysis of risky bet batch run output --- notebooks/risky_bet_batch_analysis.ipynb | 664 +++++++++++++++++++++++ 1 file changed, 664 insertions(+) create mode 100644 notebooks/risky_bet_batch_analysis.ipynb diff --git a/notebooks/risky_bet_batch_analysis.ipynb b/notebooks/risky_bet_batch_analysis.ipynb new file mode 100644 index 0000000..1a0be1f --- /dev/null +++ b/notebooks/risky_bet_batch_analysis.ipynb @@ -0,0 +1,664 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 33, + "id": "fae3476d-4db4-41af-9e14-7c6720b6f70d", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "df = pd.read_csv(\"riskybet_2023-07-18T175453_425590.csv\")" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "a09b3130-e43f-44a4-b161-30f43412f91a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
RunIditerationStepgrid_sizerisk_adjustmentprob_risky_payoffrisky_betrisk_minrisk_q1risk_meanrisk_q3risk_maxAgentIDrisk_levelchoice
000010adopt0.335726True0.0032840.2045240.4577050.7074010.987907NaNNaNNaN
100110adopt0.000580False0.0032840.2045240.4577050.7074010.9879070.00.185156Bet.RISKY
200110adopt0.000580False0.0032840.2045240.4577050.7074010.9879071.00.710948Bet.SAFE
300110adopt0.000580False0.0032840.2045240.4577050.7074010.9879072.00.763103Bet.SAFE
400110adopt0.000580False0.0032840.2045240.4577050.7074010.9879073.00.782158Bet.SAFE
\n", + "
" + ], + "text/plain": [ + " RunId iteration Step grid_size risk_adjustment prob_risky_payoff \n", + "0 0 0 0 10 adopt 0.335726 \\\n", + "1 0 0 1 10 adopt 0.000580 \n", + "2 0 0 1 10 adopt 0.000580 \n", + "3 0 0 1 10 adopt 0.000580 \n", + "4 0 0 1 10 adopt 0.000580 \n", + "\n", + " risky_bet risk_min risk_q1 risk_mean risk_q3 risk_max AgentID \n", + "0 True 0.003284 0.204524 0.457705 0.707401 0.987907 NaN \\\n", + "1 False 0.003284 0.204524 0.457705 0.707401 0.987907 0.0 \n", + "2 False 0.003284 0.204524 0.457705 0.707401 0.987907 1.0 \n", + "3 False 0.003284 0.204524 0.457705 0.707401 0.987907 2.0 \n", + "4 False 0.003284 0.204524 0.457705 0.707401 0.987907 3.0 \n", + "\n", + " risk_level choice \n", + "0 NaN NaN \n", + "1 0.185156 Bet.RISKY \n", + "2 0.710948 Bet.SAFE \n", + "3 0.763103 Bet.SAFE \n", + "4 0.782158 Bet.SAFE " + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "549276ba-fb0a-4410-a47c-7260c3c8795f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
RunIditerationStepgrid_sizerisk_adjustmentprob_risky_payoffrisky_betrisk_minrisk_q1risk_meanrisk_q3risk_maxAgentIDrisk_levelchoice
99010010010adopt0.008986False0.6281710.6671570.7425270.7920380.9879070.00.763103Bet.SAFE
99020010010adopt0.008986False0.6281710.6671570.7425270.7920380.9879071.00.987907Bet.SAFE
99030010010adopt0.008986False0.6281710.6671570.7425270.7920380.9879072.00.987907Bet.SAFE
99040010010adopt0.008986False0.6281710.6671570.7425270.7920380.9879073.00.703855Bet.SAFE
99050010010adopt0.008986False0.6281710.6671570.7425270.7920380.9879074.00.987907Bet.SAFE
................................................
140002529410030average0.794222False0.3625830.5755600.6284680.6678730.999013895.00.940166Bet.SAFE
140002629410030average0.794222False0.3625830.5755600.6284680.6678730.999013896.00.622660Bet.RISKY
140002729410030average0.794222False0.3625830.5755600.6284680.6678730.999013897.00.639917Bet.RISKY
140002829410030average0.794222False0.3625830.5755600.6284680.6678730.999013898.00.924706Bet.SAFE
140002929410030average0.794222False0.3625830.5755600.6284680.6678730.999013899.00.663706Bet.RISKY
\n", + "

14000 rows × 15 columns

\n", + "
" + ], + "text/plain": [ + " RunId iteration Step grid_size risk_adjustment prob_risky_payoff \n", + "9901 0 0 100 10 adopt 0.008986 \\\n", + "9902 0 0 100 10 adopt 0.008986 \n", + "9903 0 0 100 10 adopt 0.008986 \n", + "9904 0 0 100 10 adopt 0.008986 \n", + "9905 0 0 100 10 adopt 0.008986 \n", + "... ... ... ... ... ... ... \n", + "1400025 29 4 100 30 average 0.794222 \n", + "1400026 29 4 100 30 average 0.794222 \n", + "1400027 29 4 100 30 average 0.794222 \n", + "1400028 29 4 100 30 average 0.794222 \n", + "1400029 29 4 100 30 average 0.794222 \n", + "\n", + " risky_bet risk_min risk_q1 risk_mean risk_q3 risk_max \n", + "9901 False 0.628171 0.667157 0.742527 0.792038 0.987907 \\\n", + "9902 False 0.628171 0.667157 0.742527 0.792038 0.987907 \n", + "9903 False 0.628171 0.667157 0.742527 0.792038 0.987907 \n", + "9904 False 0.628171 0.667157 0.742527 0.792038 0.987907 \n", + "9905 False 0.628171 0.667157 0.742527 0.792038 0.987907 \n", + "... ... ... ... ... ... ... \n", + "1400025 False 0.362583 0.575560 0.628468 0.667873 0.999013 \n", + "1400026 False 0.362583 0.575560 0.628468 0.667873 0.999013 \n", + "1400027 False 0.362583 0.575560 0.628468 0.667873 0.999013 \n", + "1400028 False 0.362583 0.575560 0.628468 0.667873 0.999013 \n", + "1400029 False 0.362583 0.575560 0.628468 0.667873 0.999013 \n", + "\n", + " AgentID risk_level choice \n", + "9901 0.0 0.763103 Bet.SAFE \n", + "9902 1.0 0.987907 Bet.SAFE \n", + "9903 2.0 0.987907 Bet.SAFE \n", + "9904 3.0 0.703855 Bet.SAFE \n", + "9905 4.0 0.987907 Bet.SAFE \n", + "... ... ... ... \n", + "1400025 895.0 0.940166 Bet.SAFE \n", + "1400026 896.0 0.622660 Bet.RISKY \n", + "1400027 897.0 0.639917 Bet.RISKY \n", + "1400028 898.0 0.924706 Bet.SAFE \n", + "1400029 899.0 0.663706 Bet.RISKY \n", + "\n", + "[14000 rows x 15 columns]" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "last_step = df[df.Step == 100]\n", + "last_step" + ] + }, + { + "cell_type": "markdown", + "id": "808a06a1-76ab-4ca3-8fab-4f9f40d60857", + "metadata": {}, + "source": [ + "## overall risk distribution at end of simulations" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "f9a8cbbe-13d8-4f7b-9348-15feebe7a88a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 36, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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3nC+Znz//fet9evXW/v27bMOHjxovfzyy1ZUVJS1ZcuWRp68YXzNV+/IkSPWrbfeat1///3WiBEjGmdYP/iab9WqVZbdbreOHTvmOTmdzkae2je+ZnS5XFavXr2soUOHWjt27LCOHDlilZSUWBUVFY08ecP4mu/EiRNet9/evXutqKgoa9WqVY07eAP5mm/16tWWzWazVq9ebR05csTaunWrlZKSYk2dOrWRJ284XzNOnz7dSk1NtTZt2mQdOnTIWrZsmRUbG2t99tlnjTx5w2zevNl65plnrPXr11uSrHffffeq6w8fPmzFxcVZeXl51r59+6xXX3016I8TFJgf6d27t5Wbm+s5X1tba6WmploLFy5s0NdfvHjRio+Pt956661gjXjdrjejZVlWjx49rNmzZwdjvOvmT76LFy9a9957r/Xmm29a48ePD+kC42u+VatWWS1atGik6QLD14yvvfaa1aZNG6umpqaxRrwu1/t/cMmSJVZ8fLx19uzZYI14XXzNl5ubaw0YMMDrWF5ennXfffcFdc7r4WvGlJQU6/e//73XsYcfftgaM2ZMUOcMhIYUmOnTp1t33XWX17FHHnnEysnJCdpcvIT0AzU1NSovL1dWVpbnWGRkpLKyslRWVtagPc6fPy+3261WrVoFa8zrcr0ZLctScXGxDhw4oH79+gVzVL/4m2/+/PlKTEzUhAkTGmNMv/mb7+zZs0pPT1daWppGjBihysrKxhjXL/5kfO+995SZmanc3FwlJSWpc+fOeuGFF1RbW9tYYzdYIO5nVqxYoVGjRummm24K1ph+8yffvffeq/Lycs9LMIcPH9bmzZs1dOjQRpnZV/5kdLlcl7x027x5c+3YsSOoszaWsrIyr38PScrJyWnw97Q/QvYn8TaFv//976qtrb3kp/0mJSXpyy+/bNAeM2bMUGpq6iU3ZKjwN+Pp06d16623yuVyKSoqSsuWLdOgQYOCPa7P/Mm3Y8cOrVixQhUVFY0w4fXxJ1/79u21cuVKde3aVadPn9aiRYt07733qrKyUrfddltjjO0TfzIePnxY27dv15gxY7R582YdPHhQv/rVr+R2uzV37tzGGLvBrvd+Zvfu3dq7d69WrFgRrBGviz/5Ro8erb///e/q27evLMvSxYsX9cQTT+jXv/51Y4zsM38y5uTkaPHixerXr5/atm2r4uJirV+/PiRLtj+cTudl/z2qq6v1/fffq3nz5gG/Tp6BCaAXX3xRa9eu1bvvvmvEmyR9ER8fr4qKCn3yySd6/vnnlZeXp5KSkqYe67qdOXNGY8eO1RtvvKHWrVs39ThBkZmZqXHjxql79+564IEHtH79et1yyy36wx/+0NSjBUxdXZ0SExP1+uuvq2fPnnrkkUf0zDPPaPny5U09WsCtWLFCXbp0Ue/evZt6lIApKSnRCy+8oGXLlumzzz7T+vXrtWnTJi1YsKCpRwuYV155Rf/0T/+kDh06KCYmRpMnT9Zjjz2myEgehv3FMzA/0Lp1a0VFRamqqsrreFVVlZKTk6/6tYsWLdKLL76obdu2qWvXrsEc87r4mzEyMlLt2rWTJHXv3l379+/XwoUL1b9//2CO6zNf8x06dEhfffWVhg8f7jlWV1cnSWrWrJkOHDigtm3bBndoH1zP92i96Oho9ejRQwcPHgzGiNfNn4wpKSmKjo5WVFSU51jHjh3ldDpVU1OjmJiYoM7si+u5Dc+dO6e1a9dq/vz5wRzxuviT79lnn9XYsWP1+OOPS5K6dOmic+fOadKkSXrmmWdC7kHen4y33HKLNmzYoAsXLujEiRNKTU3VzJkz1aZNm8YYOeiSk5Mv++9ht9uD8uyLxDMwXmJiYtSzZ08VFxd7jtXV1am4uFiZmZlX/LqCggItWLBAW7ZsUa9evRpjVL/5m/HH6urq5HK5gjHidfE1X4cOHfTFF1+ooqLCc/rZz36mBx98UBUVFUpLS2vM8a8pELdfbW2tvvjiC6WkpARrzOviT8b77rtPBw8e9JRPSfqf//kfpaSkhFR5ka7vNnznnXfkcrn06KOPBntMv/mT7/z585eUlPoyaoXgr+u7ntswNjZWt956qy5evKg///nPGjFiRLDHbRSZmZle/x6S5HA4fHpc8VnQ3h5sqLVr11o2m80qLCy09u3bZ02aNMlKSEjwfOx07Nix1syZMz3rX3zxRSsmJsb6z//8T6+POZ45c6apIlyTrxlfeOEFq6ioyDp06JC1b98+a9GiRVazZs2sN954o6kiXJWv+X4s1D+F5Gu+efPmWVu3brUOHTpklZeXW6NGjbJiY2OtysrKpopwTb5mPHr0qBUfH29NnjzZOnDggLVx40YrMTHReu6555oqwlX5+z3at29f65FHHmnscX3ma765c+da8fHx1p/+9Cfr8OHDVlFRkdW2bVvrX/7lX5oqwjX5mnHXrl3Wn//8Z+vQoUNWaWmpNWDAACsjI8P67rvvmijB1Z05c8bas2ePtWfPHkuStXjxYmvPnj3W//7v/1qWZVkzZ860xo4d61lf/zHqadOmWfv377eWLl3Kx6ibwquvvmrdfvvtVkxMjNW7d29r165dnsseeOABa/z48Z7z6enplqRLTnPnzm38wX3gS8ZnnnnGateunRUbG2u1bNnSyszMtNauXdsEUzecL/l+LNQLjGX5lm/KlCmetUlJSdbQoUND9mdP/JCvt+HOnTutPn36WDabzWrTpo31/PPPWxcvXmzkqRvO13xffvmlJckqKipq5En940s+t9tt5efnW23btrViY2OttLQ061e/+lXIPrjX8yVjSUmJ1bFjR8tms1k333yzNXbsWOtvf/tbE0zdMO+///5lH9vqM40fP9564IEHLvma7t27WzExMVabNm2C/nOKIiwrBJ+fAwAAuAreAwMAAIxDgQEAAMahwAAAAONQYAAAgHEoMAAAwDgUGAAAYBwKDAAAMA4FBgAAGIcCAwAAjEOBAQAAxqHAAAAA41BgAACAcf4fEgAxSkC9tQEAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# overall ending risk distribution across all runs\n", + "last_step.risk_level.hist()" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "87aada54-d371-43e4-9554-6597ea6330e0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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ysoWnrxpgs33l7/8JeD+O3DggBUdHW2tXr26ZQdvomDzrV271nI6ndbatWutI0eOWFu2bLFSUlKsRx55pIUnb7pgMz766KNWamqq9dZbb1mffvqp9cILL1hxcXHWvn37Wnjyptm8ebP1+OOPWxs2bLAkWW+88cZF13/22WdWu3btrKKiIuvgwYPW0qVLbf8+QYH5nsGDB1sFBQX+y/X19VZqaqo1f/78Jl3/3LlzVocOHayXX37ZrhEv2+VmtCzLuv76663p06fbMd5la06+c+fOWTfeeKP1z//8z1Z+fn5YF5hg861evdrq2LFjC00XGsFmfPHFF62rrrrKqqura6kRL8vlfg0+++yzVocOHazTp0/bNeJlCTZfQUGBdfvttwccKyoqsm666SZb57wcwWZMSUmxnn/++YBjd999t5WXl2frnKHQlALz6KOPWn379g04du+991o5OTm2zcVLSN9RV1eniooKZWVl+Y9FRUUpKytL5eXlTdqjtrZWXq9XiYmJdo15WS43o2VZ2rp1qw4fPqyhQ4faOWqzNDff3Llz1bVrV02YMKElxmy25uY7ffq0unfvrrS0NI0dO1ZVVVUtMW6zNCfjm2++qczMTBUUFCgpKUn9+vXTU089pfr6+pYau8lC8TizcuVK5ebmqn379naN2WzNyXfjjTeqoqLC/xLMZ599ps2bN2vUqFEtMnOwmpPR4/E0eum2bdu2ev/9922dtaWUl5cH/HtIUk5OTpM/p5sjbH8Tb2v485//rPr6+ka/7TcpKUl/+tOfmrTHY489ptTU1EZ3ZLhobsaTJ0/qiiuukMfjUXR0tF544QWNGDHC7nGD1px877//vlauXKnKysoWmPDyNCdfr169tGrVKg0YMEAnT57UwoULdeONN6qqqkpXXnllS4wdlOZk/Oyzz7Rt2zbl5eVp8+bN+uSTT/TQQw/J6/Vq1qxZLTF2k13u48zu3bt14MABrVy50q4RL0tz8t13333685//rJtvvlmWZencuXP69a9/rd/+9rctMXLQmpMxJydHixYt0tChQ9WzZ09t3bpVGzZsCMuS3Rxut/u8/x41NTX6v//7P7Vt2zbkt8kzMCH09NNPa/369XrjjTeM+CHJYHTo0EGVlZXas2ePnnzySRUVFWn79u2tPdZlO3XqlMaPH6+XXnpJXbp0ae1xbJGZman7779fAwcO1K233qoNGzboxz/+sX73u9+19mgh4/P51LVrV61YsUKDBg3Svffeq8cff1zLly9v7dFCbuXKlerfv78GDx7c2qOEzPbt2/XUU0/phRde0L59+7Rhwwa99dZbmjdvXmuPFjLPPfecrrnmGl177bWKjY1VYWGhHnjgAUVF8W24uXgG5ju6dOmi6OhoVVdXBxyvrq5WcnLyRa+7cOFCPf3003r77bc1YMAAO8e8LM3NGBUVpauvvlqSNHDgQB06dEjz58/XsGHD7Bw3aMHm+/TTT/X5559rzJgx/mM+n0+S1KZNGx0+fFg9e/a0d+ggXM7naIOYmBhdf/31+uSTT+wY8bI1J2NKSopiYmIUHR3tP9a7d2+53W7V1dUpNjbW1pmDcTn34ZkzZ7R+/XrNnTvXzhEvS3PyzZgxQ+PHj9ff/d3fSZL69++vM2fOaNKkSXr88cfD7pt8czL++Mc/1saNG3X27Fn95S9/UWpqqqZOnaqrrrqqJUa2XXJy8nn/PeLj42159kXiGZgAsbGxGjRokLZu3eo/5vP5tHXrVmVmZl7wesXFxZo3b55KSkqUkZHREqM2W3Mzfp/P55PH47FjxMsSbL5rr71W+/fvV2Vlpf/jF7/4hW677TZVVlYqLS2tJce/pFDcf/X19dq/f79SUlLsGvOyNCfjTTfdpE8++cRfPiXpo48+UkpKSliVF+ny7sPXXntNHo9Hv/zlL+0es9mak6+2trZRSWkoo1YY/rm+y7kP4+LidMUVV+jcuXN6/fXXNXbsWLvHbRGZmZkB/x6S5HK5gvq+EjTbfjzYUOvXr7ecTqe1Zs0a6+DBg9akSZOshIQE/9tOx48fb02dOtW//umnn7ZiY2Otf//3fw94m+OpU6daK8IlBZvxqaeeskpLS61PP/3UOnjwoLVw4UKrTZs21ksvvdRaES4q2HzfF+7vQgo235w5c6wtW7ZYn376qVVRUWHl5uZacXFxVlVVVWtFuKRgMx49etTq0KGDVVhYaB0+fNjatGmT1bVrV+uJJ55orQgX1dzP0Ztvvtm69957W3rcoAWbb9asWVaHDh2s3//+99Znn31mlZaWWj179rTuueee1opwScFm3Llzp/X6669bn376qVVWVmbdfvvtVnp6uvXNN9+0UoKLO3XqlPXBBx9YH3zwgSXJWrRokfXBBx9YX3zxhWVZljV16lRr/Pjx/vUNb6OeMmWKdejQIWvZsmW8jbo1LF261OrWrZsVGxtrDR482Nq5c6f/3K233mrl5+f7L3fv3t2S1Ohj1qxZLT94EILJ+Pjjj1tXX321FRcXZ3Xq1MnKzMy01q9f3wpTN10w+b4v3AuMZQWXb/Lkyf61SUlJ1qhRo8L2d098V7D34Y4dO6whQ4ZYTqfTuuqqq6wnn3zSOnfuXAtP3XTB5vvTn/5kSbJKS0tbeNLmCSaf1+u1Zs+ebfXs2dOKi4uz0tLSrIceeihsv7k3CCbj9u3brd69e1tOp9Pq3LmzNX78eOurr75qhamb5p133jnv97aGTPn5+datt97a6DoDBw60YmNjrauuusr231PksKwwfH4OAADgIvgZGAAAYBwKDAAAMA4FBgAAGIcCAwAAjEOBAQAAxqHAAAAA41BgAACAcSgwAADAOBQYAABgHAoMAAAwDgUGAAAYhwIDAACM8/8AXhNOE30u5h4AAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# does it look any different if we change the number of bins?\n", + "last_step.risk_level.hist(bins=20)" + ] + }, + { + "cell_type": "markdown", + "id": "7fef6b97-e1d1-45cd-bcad-b0da4e2ae2af", + "metadata": {}, + "source": [ + "## histogram of ending risk levels for each simulation" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "bc8c0f48-d4bb-4197-a57b-c924bd41c649", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,\n", + " 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29])" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "last_step.RunId.unique()" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "cef78a71-4c96-4628-925b-4cc63c57d5f5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "30" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(last_step.RunId.unique())" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "id": "358468a7-f223-4410-b4bd-f614a2b339fa", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABbEAAAQrCAYAAACyzOGxAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzdeXxU5d3///dkYUgCSQjIEg2goIKsFhoJlUWIhEWoG8stN4IPCkqB3mpbRW9lEaWKCtRAQS2KaGMFRVRALSJUxYCI1S9SZRMQZRO4SSCBEJLr94e/TJnMZJlkljNnXs/HYx6aM1fOdV1nznuu4ZOZMw5jjBEAAAAAAAAAABYUFeoBAAAAAAAAAABQEYrYAAAAAAAAAADLoogNAAAAAAAAALAsitgAAAAAAAAAAMuiiA0AAAAAAAAAsCyK2AAAAAAAAAAAy6KIDQAAAAAAAACwLIrYAAAAAAAAAADLoogNAAAAAAAAALAsitjwqzFjxqhly5ZVttu3b58cDoeWLFkS8DF507JlS40ZMyYkfQPBFC6ZBFAz4ZJx1l1EinDJJICaCZeMs+4iUoRLJuEfFLGr4fTp05o2bZr69++vlJSUKk/8b775Rv3791e9evWUkpKiUaNG6aeffgq7vlE7OTk5mjdvXqiHYUtkErC3LVu2aNKkSWrXrp0SEhLUvHlzDRs2TDt37vTa3p85C2XfqB3W3cAhk4C9bd++XUOHDtVll12m+Ph4NWrUSD179tQ777zjtb0/cxbKvlE7rLuBQyYB72JCPYBwcOzYMT3yyCNq3ry5OnXqpA0bNlTY9ocfflDPnj2VlJSkWbNm6fTp03rqqae0bds2ffbZZ6pTp07Y9F0Tzz//vEpLSwPeTzjIycnR119/rbvvvjvUQ7EdMll9ZBLh6IknntDGjRs1dOhQdezYUYcPH9b8+fP1i1/8Qps2bVL79u1dbf2ds1D2XRNk/D9YdwOHTFYfmUQ42r9/v06dOqXRo0crNTVVhYWFeuONNzRkyBA9++yzGj9+vKutv3MWyr5rgoz/B+tu4JDJ6iOTEcagSmfPnjWHDh0yxhizZcsWI8m8+OKLXttOmDDBxMXFmf3797u2rV271kgyzz77bFj17YvTp0/71H7v3r2VziXQWrRoYUaPHh3QPgYNGmRatGgR0D4iFZmsWrhl0p8KCgpCPQTU0saNG01RUZHbtp07dxqn02lGjhzptt3fOQtl374It4yz7oY3Mlm1cMukP7Hu2tP58+dNp06dzJVXXum2PRg5C2XfFQm3jLPu2g+ZdBdumfSnSF53uZxINTidTjVt2rRabd944w3dcMMNat68uWtbZmamrrjiCi1btkySZIzRddddp4suukhHjx51tTt37pw6dOigVq1aqaCgICB9V+b48eMaNWqUEhMTlZycrNGjR+urr77yuFzCmDFjVK9ePe3Zs0cDBw5U/fr1NXLkSNd95a9HdPLkSY0ZM0ZJSUmu/Z48ebJac6rIiy++qD59+qhx48ZyOp266qqrtHDhQo92xhg9+uijuuSSSxQfH6/rrrtO27dv97rP7777TkOHDlVKSori4+PVrVs3rV692q3Nhg0b5HA49Nprr+nBBx9U06ZNlZCQoCFDhujAgQOudr1799bq1au1f/9+ORwOORyOal2nCdVDJsMzkzfccIMuu+wyr7+fkZGhrl27um175ZVX1KVLF8XFxSklJUUjRoxwy5n0c9bat2+vrVu3qmfPnoqPj9eDDz4oSXrrrbc0aNAgpaamyul0qlWrVpo5c6ZKSko8+l+wYIEuu+wyxcXFKT09XR9//LF69+6t3r17u7UrKirStGnT1Lp1azmdTqWlpem+++5TUVGRr4cMlejevbvHOzcuv/xytWvXTt98843bdn9n3N99V8ZuGZdYd+2KTIZnJll3URvR0dFKS0vzOFf9nXF/910Zu2VcYt2NJGTS+plk3Q08LifiRz/++KOOHj3qcWJKUnp6utasWSNJcjgceuGFF9SxY0fdddddWrFihSRp2rRp2r59uzZs2KCEhISA9F2R0tJSDR48WJ999pkmTJigNm3a6K233tLo0aO9tj9//ryysrJ07bXX6qmnnlJ8fLzXdsYY/frXv9Ynn3yiu+66S23bttWbb75Z4X6ra+HChWrXrp2GDBmimJgYvfPOO/rtb3+r0tJSTZw40dVu6tSpevTRRzVw4EANHDhQX3zxhfr166dz58657e/IkSPq3r27CgsL9bvf/U4NGzbUSy+9pCFDhuj111/XTTfd5Nb+sccek8Ph0P3336+jR49q3rx5yszM1Jdffqm4uDj97//+r/Ly8vTDDz9o7ty5kqR69erVas7wHZn0FMpMDh8+XLfffru2bNmiX/7yl67f3b9/vzZt2qQnn3zSte2xxx7Tww8/rGHDhuk3v/mNfvrpJ2VnZ6tnz57617/+peTkZFfb48ePa8CAARoxYoT++7//W02aNJEkLVmyRPXq1dO9996revXq6cMPP9TUqVOVn5/v1tfChQs1adIk9ejRQ/fcc4/27dunG2+8UQ0aNNAll1zialdaWqohQ4bok08+0fjx49W2bVtt27ZNc+fO1c6dO7Vy5cpaHUNUzhijI0eOqF27dq5twcp4bfquiB0zLrHuRhIyaf1Msu7CVwUFBTpz5ozy8vL09ttv691339Xw4cNd9wcy4/7quyJ2zLjEumt3ZPI/wiGTrLtBEOR3foe9yi4fUHbf0qVLPe774x//aCSZs2fPurY9++yzRpJ55ZVXzKZNm0x0dLS5++67g9J3eW+88YaRZObNm+faVlJSYvr06ePR5+jRo40kM2XKFI/9jB492u0jRStXrjSSzOzZs13bzp8/b3r06FGrj3IUFhZ6bMvKyjKXXXaZ6+ejR4+aOnXqmEGDBpnS0lLX9gcffNBIcvt41d13320kmY8//ti17dSpU+bSSy81LVu2NCUlJcYYY9avX28kmYsvvtjk5+e72i5btsxIMn/+859d2/h4VXCQyfDJZF5ennE6neb3v/+9W7vZs2cbh8Ph+hjavn37THR0tHnsscfc2m3bts3ExMS4be/Vq5eRZBYtWlStMd15550mPj7edeyLiopMw4YNzS9/+UtTXFzsardkyRIjyfTq1cu17eWXXzZRUVFuzxPGGLNo0SIjyWzcuNGjP/jPyy+/bCSZxYsXu7YFOuP+7Ls8O2acdTeykEnrZ5J1F7668847jSQjyURFRZlbb73VnDhxwnV/IDPu777Ls2PGWXftj0z+LFwyybobeFxOxI/OnDkj6efLDZRXt25dtzaSNH78eGVlZWny5MkaNWqUWrVqpVmzZgWl7/Lee+89xcbGaty4ca5tUVFRbn/lLW/ChAlVjmvNmjWKiYlxaxsdHa3JkydX+buViYuLc/1/Xl6ejh07pl69eum7775TXl6eJOmDDz7QuXPnNHnyZDkcDld7b188sWbNGqWnp+vaa691batXr57Gjx+vffv26d///rdb+9tvv13169d3/XzrrbeqWbNmVf61EcFFJj2FMpOJiYkaMGCAli1bJmOMq/1rr72mbt26uT6GtmLFCpWWlmrYsGE6duyY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G8iM+7vv8uyYcdZd+yOT7qyeSdbdwONyIn5UdgJ5u1bM2bNn3dqUWbx4sVq1aqVdu3bp008/9bg/kH1faP/+/WrWrJnHRzJat27ttX1MTIzbxw6q2m/5jxZdeeWVVf5uZTZu3Khp06YpNzdXhYWFbvfl5eUpKSlJ+/fvl/TzdRMvdNFFF3mEdf/+/brmmms8+mnbtq3r/vbt27u2l9+nw+FQ69attW/fvhrPCf5HJivebygyKf38EauVK1cqNzdX3bt31549e7R161bNmzfP1X7Xrl0yxnjkrExsbKzbzxdffLHXb77evn27HnroIX344YfKz8/3GJMk1/NE+eMaExPjcW21Xbt26ZtvvtFFF13kdVwXXl8O/nP48GENGjRISUlJev311xUdHe26L9AZ93ffF7Jjxll3IwOZ/Fk4ZFJi3YVv2rRpozZt2kj6uYjZr18/DR48WJs3b5bD4QhoxgPR94XsmHHWXfsjk/8RDpmUWHcDjSK2HzVr1kySdOjQIY/7Dh06pJSUFI+/Um3YsMEV+m3btikjIyNofdeG0+lUVFRo3si/Z88e9e3bV23atNGcOXOUlpamOnXqaM2aNZo7d65KS0tDMi5YD5kMDl8yOXjwYMXHx2vZsmXq3r27li1bpqioKA0dOtTVprS0VA6HQ++++65bgaJM+Rck3l4cnTx5Ur169VJiYqIeeeQRtWrVSnXr1tUXX3yh+++/v0bPE6WlperQoYPmzJnj9f60tDSf94nK5eXlacCAATp58qQ+/vhjpaamut0fyIwHou/aCJeMw97I5H+ESyZZd1Ebt956q+68807t3LlTV155ZVBfW/uj79oIl4wjspBJ62eSdTewKGL70cUXX6yLLrpIn3/+ucd9n332mTp37uy27dChQ5o8ebL69eunOnXq6A9/+IOysrLUokWLgPddXosWLbR+/XoVFha6/SVs9+7dPo+l/H7XrVun06dPuwVxx44dNd7nO++8o6KiIr399ttu34Jb/qMNZcdx165dbt8Q+9NPP+n//u//PNp6G1PZRzDKPya7du1y+9kYo927d6tjx46ubRd+pAuhQSa97zdUmZSkhIQE3XDDDVq+fLnmzJmj1157TT169HArRrRq1UrGGF166aW64oorajSmDRs26Pjx41qxYoV69uzp2r537163dmWP7e7du3Xddde5tp8/f1779u1zy3SrVq301VdfqW/fvuQ7CM6ePavBgwdr586d+uCDD3TVVVd5tAlUxgPRd3l2zDjrrr2RyZph3WXdDWdllwIoe0dfMF9b17bv8uyYcdbdyEMmq8a6a+91l2ti+9ktt9yiVatW6cCBA65t69at086dO93+8iJJ48aNU2lpqRYvXqznnntOMTExGjt2rNu1cwLVd3lZWVkqLi7W888/79pWWlqqBQsW1GgsZQYOHKjz589r4cKFrm0lJSXKzs6u8T7L/lJ14XHKy8vTiy++6NYuMzNTsbGxys7Odmt74cc4LhznZ599ptzcXNe2goICPffcc2rZsqXHP5aWLl2qU6dOuX5+/fXXdejQIQ0YMMC1LSEhwfUEj9Ahk+5Cmckyw4cP18GDB/XXv/5VX331ldu3XEvSzTffrOjoaM2YMcPj2BtjdPz48RqN6dy5c/rLX/7i1q5r165q2LChnn/+eZ0/f961/W9/+5vHi/9hw4bpxx9/dHtMypw5c0YFBQVVjgvVU1JSouHDhys3N1fLly+v9B0i/s54oPouz44ZZ921LzJZc6y7rLvhwNtHxIuLi7V06VLFxcW5PSf7O+OB6rs8O2acdde+yGTNse7afN0N7PdG2kd2draZOXOmmTBhgpFkbr75ZjNz5kwzc+ZMc/LkSVe777//3jRs2NC0atXKPPPMM2bWrFmmQYMGpkOHDm7fzPrCCy8YSWbJkiWuba+88oqRZBYsWBDQvr05f/68SU9PN9HR0WbSpElm/vz5pl+/fqZz584e4xw9erRJSEjwup/y3wxbUlJifvWrX5moqCjz29/+1syfP9/06dPHdOzY0eObYffu3evxLcrefPvtt6ZOnTqmQ4cOZv78+ebxxx83rVq1Mp06dTKSzN69e11tH3jgASPJDBw40MyfP9+MHTvWpKammkaNGrn1c/jwYdOkSROTlJRkHn74YTN37lzTuXNn43A4zIoVK1ztyr6tuUOHDqZjx45m7ty5ZsqUKaZu3bqmdevWpqCgwNV29uzZRpK55557TE5Ojnn77bcrnRd8QybDM5PGGHPmzBlTv359U79+fRMdHW2OHDnisc8//elPRpLp3r27mT17tlm4cKG57777zOWXX26efPJJV7tevXqZdu3aefz+sWPHTIMGDUyLFi3M008/bebMmWOuvvpq15jWr1/vapudnW0kmR49epjs7Gzz+9//3vW49e7d2+3YDRw40DgcDjNixAiTnZ1t5s2bZ+666y6TkpJitmzZUulxQvX9z//8j5FkBg8ebF5++WWP24X8nfFA9O2NXTPOumtPZDJ8M2kM6y6qduONN5o+ffqY6dOnm+eff97MnDnTtGnTxkgyTz/9tFtbf2c8EH17Y9eMs+7aE5kM30waw7obSBSxq6lFixZGktdb+RP266+/Nv369TPx8fEmOTnZjBw50hw+fNh1/4EDB0xSUpIZPHiwRz833XSTSUhIMN99911A+q7MTz/9ZG677TZTv359k5SUZMaMGWM2btxoJJm///3vrna+PIEYY8zx48fNqFGjTGJioklKSjKjRo0y//rXvzyeQLZt22YkmSlTplQ51rffftt07NjR1K1b17Rs2dI88cQTriflC49JSUmJmTFjhmnWrJmJi4szvXv3Nl9//bVp0aKFxxPVnj17zK233mqSk5NN3bp1TXp6ulm1apVbm7JF/dVXXzUPPPCAady4sYmLizODBg0y+/fvd2t7+vRpc9ttt5nk5GQjyeO4oHbIZHhmsszIkSONJJOZmVnhPt944w1z7bXXmoSEBJOQkGDatGljJk6caHbs2OFqU9GibowxGzduNN26dTNxcXEmNTXV3Hfffeb999/3WNSNMeaZZ54xLVq0ME6n06Snp5uNGzeaLl26mP79+7u1O3funHniiSdMu3btjNPpNA0aNDBdunQxM2bMMHl5eVUeJ1RPr169KsyYt7+/+zPj/u67MnbMOOuuPZHJ8M1kGdZdVObVV181mZmZpkmTJiYmJsY0aNDAZGZmmrfeestre39m3N99V8aOGWfdtScyGb6ZLMO6GxgUsVGpN99800gyn3zyScD7WrBggUlISKj2E14olC3qy5cvD/VQEKHIZHCUlJSYlJQU85vf/CbUQ0GEIePuWHcRamQyOFh3ESpk3B3rLkKNTAZHuK67XBMbLmUX6i9Tdt2gxMRE/eIXvwh4/+vXr9fvfvc7NWnSJOB9AeGATAbH2bNnPa5FtnTpUp04cUK9e/cOzaAQEcg4YC1kMjhYdxEqZBywFjIZHHZad2NCPQBYx+TJk3XmzBllZGSoqKhIK1as0KeffqpZs2YpLi4u4P0vX7484H0A4YRMBsemTZt0zz33aOjQoWrYsKG++OILLV68WO3bt6/yC0mA2iDj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Ll26KC4uTikpKRoxYoRbzqSfs9a+fXtt3bpVPXv2VHx8vB588EFJ0ltvvaVBgwYpNTVVTqdTrVq10syZM1VSUuLR/4IFC3TZZZcpLi5O6enp+vjjj9W7d2/17t3brV1RUZGmTZum1q1by+l0Ki0tTffdd5+Kiop8PWSoRPfu3T3euXH55ZerXbt2+uabb9y2+zvj/u67MnbLuMS6a1dkMjwzybqL2oiOjlZaWprHuervjPu778rYLeMS624kIZPWzyTrbuBxORE/+vHHH3X06FGPE1OS0tPTtWbNGkmSw+HQCy+8oI4dO+quu+7SihUrJEnTpk3T9u3btWHDBiUkJASk74qUlpZq8ODB+uyzzzRhwgS1adNGb731lkaPHu21/fnz55WVlaVrr71WTz31lOLj4722M8bo17/+tT755BPdddddatu2rd58880K91tdCxcuVLt27TRkyBDFxMTonXfe0W9/+1uVlpZq4sSJrnZTp07Vo48+qoEDB2rgwIH64osv1K9fP507d85tf0eOHFH37t1VWFio3/3ud2rYsKFeeuklDRkyRK+//rpuuukmt/aPPfaYHA6H7r//fh09elTz5s1TZmamvvzyS8XFxel///d/lZeXpx9++EFz586VJNWrV69Wc4bvyKSnUGZy+PDhuv3227Vlyxb98pe/dP3u/v37tWnTJj355JOubY899pgefvhhDRs2TL/5zW/0008/KTs7Wz179tS//vUvJScnu9oeP35cAwYM0IgRI/Tf//3fatKkiSRpyZIlqlevnu69917Vq1dPH374oaZOnar8/Hy3vhYuXKhJkyapR48euueee7Rv3z7deOONatCggS655BJXu9LSUg0ZMkSffPKJxo8fr7Zt22rbtm2aO3eudu7cqZUrV9bqGKJyxhgdOXJE7dq1c20LVsZr03dF7JhxiXU3kpBJ62eSdRe+Kigo0JkzZ5SXl6e3335b7777roYPH+66P5AZ91ffFbFjxiXWXbsjk/8RDplk3Q2CIL/zO+xVdvmAsvuWLl3qcd8f//hHI8mcPXvWte3ZZ581kswrr7xiNm3aZKKjo83dd98dlL7Le+ONN4wkM2/ePNe2kpIS06dPH48+R48ebSSZKVOmeOxn9OjRbh8pWrlypZFkZs+e7dp2/vx506NHj1p9lKOwsNBjW1ZWlrnssstcPx89etTUqVPHDBo0yJSWlrq2P/jgg0aS28er7r77biPJfPzxx65tp06dMpdeeqlp2bKlKSkpMcYYs379eiPJXHzxxSY/P9/VdtmyZUaS+fOf/+zaxsergoNMhk8m8/LyjNPpNL///e/d2s2ePds4HA7Xx9D27dtnoqOjzWOPPebWbtu2bSYmJsZte69evYwks2jRomqN6c477zTx8fGuY19UVGQaNmxofvnLX5ri4mJXuyVLlhhJplevXq5tL7/8somKinJ7njDGmEWLFhlJZuPGjR79wX9efvllI8ksXrzYtS3QGfdn3+XZMeOsu5GFTFo/k6y78NWdd95pJBlJJioqytx6663mxIkTrvsDmXF/912eHTPOumt/ZPJn4ZJJ1t3A43IifnTmzBlJP19uoLy6deu6tZGk8ePHKysrS5MnT9aoUaPUqlUrzZo1Kyh9l/fee+8pNjZW48aNc22Liopy+ytveRMmTKhyXGvWrFFMTIxb2+joaE2ePLnK361MXFyc6//z8vJ07Ngx9erVS999953y8vIkSR988IHOnTunyZMny+FwuNp7++KJNWvWKD09Xddee61rW7169TR+/Hjt27dP//73v93a33777apfv77r51tvvVXNmjWr8q+NCC4y6SmUmUxMTNSAAQO0bNkyGWNc7V977TV169bN9TG0FStWqLS0VMOGDdOxY8dct6ZNm+ryyy/X+vXr3fp2Op264447Kh3TqVOndOzYMfXo0UOFhYX69ttvJUmff/65jh8/rnHjxikm5j8fTho5cqQaNGjgtr/ly5erbdu2atOmjdu4+vTpI0ke44L/fPvtt5o4caIyMjLc3kURjIz7q+/y7Jhx1t3IQSZ/ZvVMsu7CV3fffbfWrl2rl156SQMGDFBJSYnbO3oDmXF/912eHTPOumt/ZNKd1TPJuht4XE7Ej8pOIG/Xijl79qxbmzKLFy9Wq1attGvXLn366ace9wey7wvt379fzZo18/hIRuvWrb22j4mJcfvYQVX7Lf/RoiuvvLLK363Mxo0bNW3aNOXm5qqwsNDtvry8PCUlJWn//v2Sfr5u4oUuuugij7Du379f11xzjUc/bdu2dd3fvn171/by+3Q4HGrdurX27dtX4znB/8hkxfsNRSalnz9itXLlSuXm5qp79+7as2ePtm7dqnnz5rna79q1S8YYj5yViY2Ndfv54osv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3f7YLeMS466TkEnrZ5JxN/y4nUgIffPNN/r2228rHJiSlJWVpTVr1kiSXC6XHn/8cXXq1Em33XabXnjhBUnSjBkztGPHDm3YsEF16tQJy7orU1JSokGDBun999/XuHHj1LZtW7388ssaOXKkz/Znz55V37591b17dz344INKTk722c4Yo1/84hd67733dNttt6ldu3Z68cUXK33dQD366KNq3769Bg8erISEBL366qv67W9/q5KSEo0fP97Tbvr06br33ns1YMAADRgwQB999JH69OmjM2fOeL3e4cOHdfnll+vUqVP63e9+p4YNG+qJJ57Q4MGD9c9//lPXXXedV/v77rtPLpdLd955p7799lstWrRIOTk5+uSTT5SUlKQ//OEPys/P19dff62FCxdKkurWrVujmhE8MllRNDN5880365ZbbtEHH3ygSy+91PO7Bw4c0JYtW/SnP/3Js+y+++7TPffco5tuukm//vWv9d1332nx4sW66qqr9PHHH6t+/fqetnl5eerfv7+GDh2qX/3qV2ratKkkacWKFapbt66mTJmiunXr6s0339T06dNVUFDgta5HH31UEyZM0JVXXqnJkydr//79uvbaa9WgQQOde+65nnYlJSUaPHiw3nvvPY0dO1bt2rXTp59+qoULF+qLL77QSy+9VKNtCP+MMTp8+LDat2/vWRapjNdk3ZWxY8Ylxl0nIZPWzyTjLoJ18uRJ/fDDD8rPz9crr7yif/3rX7r55ps9z4cz46Fad2XsmHGJcdfuyORPYiGTjLsREOErv2Oev9sHlD735JNPVnjujjvuMJLM6dOnPcv++te/GknmH//4h9myZYuJj483kyZNisi6y3v++eeNJLNo0SLPsuLiYtOrV68K6xw5cqSRZKZNm1bhdUaOHOn1kaKXXnrJSDLz58/3LDt79qy58sora/RRjlOnTlVY1rdvX9O6dWvPz99++62pVauWGThwoCkpKfEsv/vuu40kr49XTZo0yUgy7777rmfZ8ePHTatWrUzLli1NcXGxMcaYt956y0gy55xzjikoKPC0fe6554wk85e//MWzjI9XRQaZjJ1M5ufnG7fbbW6//XavdvPnzzcul8vzMbT9+/eb+Ph4c99993m1+/TTT01CQoLX8h49ehhJZunSpQH16Te/+Y1JTk72bPvCwkLTsGFDc+mll5qioiJPuxUrVhhJpkePHp5lf//7301cXJzXecIYY5YuXWokmY0bN1ZYH0Ln73//u5Fkli1b5lkW7oyHct3l2THjjLvOQiatn0nGXQTrN7/5jZFkJJm4uDhzww03mKNHj3qeD2fGQ73u8uyYccZd+yOTP4qVTDLuhh+3EwmhH374QdKPtxsor3bt2l5tJGns2LHq27evJk6cqBEjRqhNmzaaO3duRNZd3tq1a5WYmKgxY8Z4lsXFxXn9lbe8cePGVdmvNWvWKCEhwattfHy8Jk6cWOXv+pOUlOT5//z8fB05ckQ9evTQ3r17lZ+fL0l64403dObMGU2cOFEul8vT3tcXT6xZs0ZZWVnq3r27Z1ndunU1duxY7d+/X//73/+82t9yyy2qV6+e5+cbbrhBzZs3r/KvjYgsMllRNDOZkpKi/v3767nnnpMxxtP+2Wef1WWXXeb5GNoLL7ygkpIS3XTTTTpy5Ijn0axZM51//vl66623vNbtdrt16623+u3T8ePHdeTIEV155ZU6deqUPv/8c0nShx9+qLy8PI0ZM0YJCT99OGn48OFq0KCB1+utWrVK7dq1U9u2bb361atXL0mq0C+Ezueff67x48crOzvb6yqKSGQ8VOsuz44ZZ9x1DjL5I6tnknEXwZo0aZLWrVunJ554Qv3791dxcbHXFb3hzHio112eHTPOuGt/ZNKb1TPJuBt+3E4khEoPIF/3ijl9+rRXm1LLli1TmzZttGvXLm3atKnC8+Fcd1kHDhxQ8+bNK3wk47zzzvPZPiEhwetjB1W9bvmPFl144YVV/q4/Gzdu1IwZM7R582adOnXK67n8/HylpqbqwIEDkn68b2JZjRs3rhDWAwcOqFu3bhXW065dO8/zHTp08Cwv/5oul0vnnXee9u/fX+2aEHpksvLXjUYmpR8/YvXSSy9p8+bNuvzyy7Vnzx5t27ZNixYt8rTftWuXjDEVclYqMTHR6+dzzjnH5zdf79ixQ3/84x/15ptvqqCgoEKfJHnOE+W3a0JCQoV7q+3atUufffaZGjdu7LNfZe8vh9DJzc3VwIEDlZqaqn/+85+Kj4/3PBfujId63WXZMeOMu85AJn8UC5mUGHcRnLZt26pt27aSfpzE7NOnjwYNGqStW7fK5XKFNePhWHdZdsw44679kcmfxEImJcbdcGMSO4SaN28uSTp06FCF5w4dOqS0tLQKf6XasGGDJ/SffvqpsrOzI7bumnC73YqLi86F/Hv27FHv3r3Vtm1bLViwQBkZGapVq5bWrFmjhQsXqqSkJCr9gvWQycgIJpODBg1ScnKynnvuOV1++eV67rnnFBcXpxtvvNHTpqSkRC6XS//617+8JihKlX9D4uvN0bFjx9SjRw+lpKRo9uzZatOmjWrXrq2PPvpId955Z7XOEyUlJerYsaMWLFjg8/mMjIygXxP+5efnq3///jp27Jjeffddpaenez0fzoyHY901ESsZh72RyZ/ESiYZd1ETN9xwg37zm9/oiy++0IUXXhjR99ahWHdNxErG4Sxk0vqZZNwNLyaxQ+icc85R48aN9eGHH1Z47v3331fnzp29lh06dEgTJ05Unz59VKtWLf3+979X3759lZmZGfZ1l5eZmam33npLp06d8vpL2O7du4PuS/nXXb9+vU6cOOEVxJ07d1b7NV999VUVFhbqlVde8foW3PIfbSjdjrt27fL6htjvvvtO33//fYW2vvpU+hGM8vtk165dXj8bY7R792516tTJs6zsR7oQHWTS9+tGK5OSVKdOHf385z/XqlWrtGDBAj377LO68sorvSYj2rRpI2OMWrVqpQsuuKBafdqwYYPy8vL0wgsv6KqrrvIs37dvn1e70n27e/duXX311Z7lZ8+e1f79+70y3aZNG/3nP/9R7969yXcEnD59WoMGDdIXX3yhN954QxdddFGFNuHKeDjWXZ4dM864a29ksnoYdxl3Y1nprQBKr+iL5Hvrmq67PDtmnHHXechk1Rh37T3uck/sEBsyZIhee+01ffXVV55l69ev1xdffOH1lxdJGjNmjEpKSrRs2TL97W9/U0JCgkaPHu1175xwrbu8vn37qqioSI899phnWUlJiZYsWVKtvpQaMGCAzp49q0cffdSzrLi4WIsXL672a5b+parsdsrPz9fy5cu92uXk5CgxMVGLFy/2alv2Yxxl+/n+++9r8+bNnmUnT57U3/72N7Vs2bLCP5aefPJJHT9+3PPzP//5Tx06dEj9+/f3LKtTp47nBI/oIZPeopnJUjfffLMOHjyo//u//9N//vMfr2+5lqTrr79e8fHxmjVrVoVtb4xRXl5etfp05swZPfLII17tunbtqoYNG+qxxx7T2bNnPcufeuqpCm/+b7rpJn3zzTde+6TUDz/8oJMn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iJVMIjSYxA7AiRMnNGPGDPXr109paWlVHvifffaZ+vXrp7p16yotLU0jRozQd999F3PrRs2sXLlSixYtinY3bIlMAvb2wQcfaMKECWrfvr3q1KmjFi1a6KabbtIXX3zhs30ocxbNdaNmGHfDh0wC9rZjxw7deOONat26tZKTk9WoUSNdddVVevXVV322D2XOorlu1AzjbviQScC3hGh3IBYcOXJEs2fPVosWLXTxxRdrw4YNlbb9+uuvddVVVyk1NVVz587ViRMn9OCDD+rTTz/V+++/r1q1asXMuqvjscceU0lJSdjXEwtWrlyp7du3a9KkSdHuiu2QycCRScSiBx54QBs3btSNN96oTp06KTc3Vw8//LB+9rOfacuWLerQoYOnbahzFs11VwcZ/wnjbviQycCRScSiAwcO6Pjx4xo5cqTS09N16tQpPf/88xo8eLD++te/auzYsZ62oc5ZNNddHWT8J4y74UMmA0cmHcagSqdPnzaHDh0yxhjzwQcfGElm+fLlPtuOGzfOJCUlmQMHDniWrVu3zkgyf/3rX2Nq3cE4ceJEUO337dvnt5Zwy8zMNCNHjgzrOgYOHGgyMzPDug6nIpNVi7VMhtLJkyej3QXU0MaNG01hYaHXsi+++MK43W4zfPhwr+Whzlk01x2MWMs4425sI5NVi7VMhhLjrj2dPXvWXHzxxebCCy/0Wh6JnEVz3ZWJtYwz7toPmfQWa5kMJSePu9xOJABut1vNmjULqO3zzz+vn//852rRooVnWU5Oji644AI999xzkiRjjK6++mo1btxY3377rafdmTNn1LFjR7Vp00YnT54My7r9ycvL04gRI5SSkqL69etr5MiR+s9//lPhdgmjRo1S3bp1tWfPHg0YMED16tXT8OHDPc+Vvx/RsWPHNGrUKKWmpnpe99ixYwHVVJnly5erV69eatKkidxuty666CI9+uijFdoZY3Tvvffq3HPPVXJysq6++mrt2LHD52vu3btXN954o9LS0pScnKzLLrtMq1ev9mqzYcMGuVwuPfvss7r77rvVrFkz1alTR4MHD9ZXX33ladezZ0+tXr1aBw4ckMvlksvlCug+TQgMmYzNTP785z9X69atff5+dna2unbt6rXsH//4h7p06aKkpCSlpaVp6NChXjmTfsxahw4dtG3bNl111VVKTk7W3XffLUl6+eWXNXDgQKWnp8vtdqtNmzaaM2eOiouLK6x/yZIlat26tZKSkpSVlaV3331XPXv2VM+ePb3aFRYWasaMGTrvvPPkdruVkZGhqVOnqrCwMNhNBj8uv/zyCldunH/++Wrfvr0+++wzr+Whznio1+2P3TIuMe7aFZmMzUwy7qIm4uPjlZGRUeFYDXXGQ71uf+yWcYlx10nIpPUzybgbftxOJIS++eYbffvttxUOTEnKysrSmjVrJEkul0uPP/64OnXqpNtuu00vvPCCJGnGjBnasWOHNmzYoDp16oRl3ZUpKSnRoEGD9P7772vcuHFq27atXn75ZY0cOdJn+7Nnz6pv377q3r27HnzwQSUnJ/tsZ4zRL37xC7333nu67bbb1K5dO7344ouVvm6gHn30UbVv316DBw9WQkKCXn31Vf32t79VSUmJxo8f72k3ffp03XvvvRowYIAGDBigjz76SH369NGZM2e8Xu/w4cO6/PLLderUKf3ud79Tw4YN9cQTT2jw4MH65z//qeuuu86r/X333SeXy6U777xT3377rRYtWqScnBx98sknSkpK0h/+8Afl5+fr66+/1sKFCyVJdevWrVHNCB6ZrCiambz55pt1yy236IMPPtCll17q+d0DBw5oy5Yt+tOf/uRZdt999+mee+7RTTfdpF//+tf67rvvtHjxYl111VX6+OOPVb9+fU/bvLw89e/fX0OHDtWvfvUrNW3aVJK0YsUK1a1bV1OmTFHdunX15ptvavr06SooKPBa16OPPqoJEyboyiuv1OTJk7V//35de+21atCggc4991xPu5KSEg0ePFjvvfeexo4dq3bt2unTTz/VwoUL9cUXX+ill16q0TaEf8YYHT58WO3bt/csi1TGa7Luytgx4xLjrpOQSetnknEXwTp58qR++OEH5efn65VXXtG//vUv3XzzzZ7nw5nxUK27MnbMuMS4a3dk8iexkEnG3QiI8JXfMc/f7QNKn3vyyScrPHfHHXcYSeb06dOeZX/961+NJPOPf/zDbNmyxcTHx5tJkyZFZN3lPf/880aSWbRokWdZcXGx6dWrV4V1jhw50kgy06ZNq/A6I0eO9PpI0UsvvWQkmfnz53uWnT171lx55ZU1+ijHqVOnKizr27evad26tefnb7/91tSqVcsMHDjQlJSUeJbffffdRpLXx6smTZpkJJl3333Xs+z4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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# plot a histogram of ending risk levels for each run\n", + "# combine into a grid of plots\n", + "import matplotlib.pyplot as plt\n", + "fig, ax = plt.subplots(5, 6, sharex='col', sharey='row', figsize=(18,13))\n", + "\n", + "for run in last_step.RunId.unique():\n", + " run_last_step = last_step[last_step.RunId == run]\n", + " plot_location = ax[int(run/6), int(run % 6)]\n", + " run_last_step.risk_level.hist(ax=plot_location, bins=20)\n", + " # use grid size and risk adjustment strategy to title the plot\n", + " grid_size = run_last_step.iloc[0].grid_size\n", + " plot_location.set_title(\"%dx%d grid, %s\" % (grid_size, grid_size, run_last_step.iloc[0].risk_adjustment))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From ef7ae22eed35a9f1c0397278bb35ba6a85a9f4e8 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 18 Jul 2023 18:28:11 -0400 Subject: [PATCH 037/141] Configure source directory; exclude notebooks dir when installing --- pyproject.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 3abec88..e7846c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,10 @@ dependencies = [ ] dynamic = ["version"] +[tool.setuptools.packages.find] +include = ["simulatingrisk"] +# exclude tests and notebooks + [project.optional-dependencies] dev = ["pre-commit", "pytest", "pytest-cov"] From fef2164db04b2cd9fc3499590c702e0e1a2f568d Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 18 Jul 2023 18:32:01 -0400 Subject: [PATCH 038/141] Set version and readme dynamically in pyproject --- pyproject.toml | 9 ++++++--- simulatingrisk/__init__.py | 6 ++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e7846c0..d5d2058 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,9 +3,8 @@ requires = ["setuptools", "setuptools-scm"] build-backend = "setuptools.build_meta" [project] -name = "simulating_risk" +name = "simulatingrisk" description = "Agent-based modeling for simulations related to risk and rationality" -readme = "README.md" requires-python = ">=3.7" license = {text = "Apache-2"} classifiers = [ @@ -14,7 +13,11 @@ classifiers = [ dependencies = [ "mesa", ] -dynamic = ["version"] +dynamic = ["version", "readme"] + +[tool.setuptools.dynamic] +version = {attr = "simulatingrisk.__version__"} +readme = {file = ["README.md"]} [tool.setuptools.packages.find] include = ["simulatingrisk"] diff --git a/simulatingrisk/__init__.py b/simulatingrisk/__init__.py index e69de29..3e25848 100644 --- a/simulatingrisk/__init__.py +++ b/simulatingrisk/__init__.py @@ -0,0 +1,6 @@ +__version_info__ = (0, 1, 0, "dev") + +# Dot-connect all but the last. Last is dash-connected if not None. +__version__ = ".".join([str(i) for i in __version_info__[:-1]]) +if __version_info__[-1] is not None: + __version__ += "-%s" % (__version_info__[-1],) From 8cdea17c6fbb90e94954d291d1f728e751b6c777 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 19 Jul 2023 12:07:39 -0400 Subject: [PATCH 039/141] Add risky food simulation to batch run script --- simulatingrisk/batch_run.py | 44 +++++++++++++++++++++++++++--- simulatingrisk/risky_food/model.py | 19 +++++++++++-- 2 files changed, 56 insertions(+), 7 deletions(-) mode change 100644 => 100755 simulatingrisk/batch_run.py diff --git a/simulatingrisk/batch_run.py b/simulatingrisk/batch_run.py old mode 100644 new mode 100755 index 22b75b1..e3cd1a9 --- a/simulatingrisk/batch_run.py +++ b/simulatingrisk/batch_run.py @@ -1,9 +1,13 @@ +#!/usr/bin/env python + +import argparse import csv from datetime import datetime from mesa import batch_run from simulatingrisk.risky_bet.model import RiskyBetModel +from simulatingrisk.risky_food.model import RiskyFoodModel def riskybet_batch_run(): @@ -15,15 +19,35 @@ def riskybet_batch_run(): }, iterations=5, max_steps=100, - number_processes=1, # set None to use all available; set 1 for jypeter + number_processes=1, # set None to use all available; set 1 for jupyter data_collection_period=1, display_progress=True, ) - # returns a list of dictionaries that can be opened with pandas; + # returns a list of dictionaries from data collection across all runs + return results + + +def riskyfood_batch_run(): + results = batch_run( + RiskyFoodModel, + # only parameter to this one currently is number of agents + parameters={ + "n": 10, # [10, 20, 30], # 100], + }, + iterations=5, + max_steps=22, # population gets too large after 25/26 rounds... + number_processes=1, # set None to use all available; set 1 for jupyter + data_collection_period=1, + display_progress=True, + ) + return results + + +def save_results(simulation, data): # save as csv for external analysis # - use datetime to distinguish this run, but make nicer for filename datestr = datetime.today().isoformat().replace(".", "_").replace(":", "") - output_filename = "riskybet_%s.csv" % datestr + output_filename = "%s_%s.csv" % (simulation, datestr) print("Saving data collection results to: %s" % output_filename) # get field names from last entry, since first entry is for the model # and doesn't include agent-level data @@ -35,4 +59,16 @@ def riskybet_batch_run(): if __name__ == "__main__": - riskybet_batch_run() + parser = argparse.ArgumentParser( + prog="simulatingrisk batch_run", + description="Run simulations in batch mode and save collected data", + ) + parser.add_argument("simulation", choices=["riskybet", "riskyfood"]) + args = parser.parse_args() + + if args.simulation == "riskybet": + results = riskybet_batch_run() + elif args.simulation == "riskyfood": + results = riskyfood_batch_run() + + save_results(args.simulation, results) diff --git a/simulatingrisk/risky_food/model.py b/simulatingrisk/risky_food/model.py index 35ec9ae..7cad45e 100644 --- a/simulatingrisk/risky_food/model.py +++ b/simulatingrisk/risky_food/model.py @@ -1,4 +1,5 @@ from enum import Enum +from functools import cached_property from statistics import mean import mesa @@ -33,6 +34,7 @@ def step(self): class RiskyFoodModel(mesa.Model): prob_notcontaminated = None + running = True # required for batch running def __init__(self, n): self.num_agents = n @@ -69,6 +71,9 @@ def step(self): # setup agents for the next round self.propagate() + # delete cached property before the next round + del self.agent_risk_levels + def get_risky_food_status(self): # determine actual food status for this round, # weighted by probability of non-contamination @@ -114,17 +119,25 @@ def agents(self): def total_agents(self): return self.schedule.get_agent_count() + @cached_property + def agent_risk_levels(self) -> [float]: + # list of all risk levels for all current agents; + # property is cached but should be cleared in each new round + + # NOTE: occasionally median method is complaining that this is empty + return [a.risk_level for a in self.agents] + @property def avg_risk_level(self): - return mean([agent.risk_level for agent in self.agents]) + return mean(self.agent_risk_levels) @property def min_risk_level(self): - return min([agent.risk_level for agent in self.agents]) + return min(self.agent_risk_levels) @property def max_risk_level(self): - return max([agent.risk_level for agent in self.agents]) + return max(self.agent_risk_levels) def payoff(self, choice): "Calculate the payoff for a given choice, based on current food status" From 746cd69589d20ef64a5485260f3817f10b012035 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 19 Jul 2023 12:07:51 -0400 Subject: [PATCH 040/141] Document how to run simulations (interactive & batch) in project readme --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 5be6f8b..7001dd2 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,16 @@ pip install -e . pip install -e ".[dev]" ``` +### Running the simulations + +- Simulations can be run interactively with mesa runserver by specifying + the path to the model, e.g. `mesa runserver simulatingrisk/risky_bet/` + Refer to the readme for each model for more details. +- Simulations can be run in batches to aggregate data across multiple + runs and different parameters. For example, + `./simulatingrisk/batch_run.py riskyfood` + + ### Install pre-commit hooks Install pre-commit hooks (currently [black](https://github.com/psf/black) and [ruff](https://beta.ruff.rs/docs/)): From e8a0d4dabffd3e526b15302430b56e7dba0d1d1d Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 19 Jul 2023 12:16:55 -0400 Subject: [PATCH 041/141] Add preliminary analysis of risky food batch run --- notebooks/riskyfood_batch_analysis.ipynb | 645 +++++++++++++++++++++++ 1 file changed, 645 insertions(+) create mode 100644 notebooks/riskyfood_batch_analysis.ipynb diff --git a/notebooks/riskyfood_batch_analysis.ipynb b/notebooks/riskyfood_batch_analysis.ipynb new file mode 100644 index 0000000..7a84e67 --- /dev/null +++ b/notebooks/riskyfood_batch_analysis.ipynb @@ -0,0 +1,645 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "e7850a1d-09ba-42d9-a59d-7b93533b1274", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "\n", + "df = pd.read_csv(\"../riskyfood_2023-07-19T113234_405932.csv\")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "73a2dc14-aadc-4bde-afee-47db039f2f65", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
RunIditerationStepnprob_notcontaminatedcontaminatedaverage_risk_levelmin_risk_levelmax_risk_levelnum_agentsAgentIDrisk_levelpayoff
0000100.90922210.5477080.012390.96156210NaNNaNNaN
1001100.98147900.5259260.012390.961562190.00.8184382.0
2001100.98147900.5259260.012390.961562191.00.9615621.0
3001100.98147900.5259260.012390.961562192.00.0959352.0
4001100.98147900.5259260.012390.961562193.00.0123902.0
\n", + "
" + ], + "text/plain": [ + " RunId iteration Step n prob_notcontaminated contaminated \n", + "0 0 0 0 10 0.909222 1 \\\n", + "1 0 0 1 10 0.981479 0 \n", + "2 0 0 1 10 0.981479 0 \n", + "3 0 0 1 10 0.981479 0 \n", + "4 0 0 1 10 0.981479 0 \n", + "\n", + " average_risk_level min_risk_level max_risk_level num_agents AgentID \n", + "0 0.547708 0.01239 0.961562 10 NaN \\\n", + "1 0.525926 0.01239 0.961562 19 0.0 \n", + "2 0.525926 0.01239 0.961562 19 1.0 \n", + "3 0.525926 0.01239 0.961562 19 2.0 \n", + "4 0.525926 0.01239 0.961562 19 3.0 \n", + "\n", + " risk_level payoff \n", + "0 NaN NaN \n", + "1 0.818438 2.0 \n", + "2 0.961562 1.0 \n", + "3 0.095935 2.0 \n", + "4 0.012390 2.0 " + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "20d06121-8270-4517-b856-963d47e06781", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Step\n", + "22 18267295\n", + "21 8169007\n", + "20 5594441\n", + "19 2640823\n", + "18 1716515\n", + "17 796653\n", + "16 441027\n", + "15 291215\n", + "14 219864\n", + "13 116250\n", + "12 51708\n", + "11 23970\n", + "10 14406\n", + "9 6748\n", + "8 3604\n", + "7 1687\n", + "6 1135\n", + "5 563\n", + "4 299\n", + "3 153\n", + "2 84\n", + "1 50\n", + "0 5\n", + "Name: count, dtype: int64" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# we couldn't do 100 steps for these because of the population explosion problem\n", + "# how many steps did we end up with?\n", + "df.Step.value_counts()\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "1b79f2fb-310e-4007-8e90-633848c3e3d6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
RunIditerationStepnprob_notcontaminatedcontaminatedaverage_risk_levelmin_risk_levelmax_risk_levelnum_agentsAgentIDrisk_levelpayoff
98571520022100.30569000.4639170.0123900.961562127349500.00.8184381.0
98571530022100.30569000.4639170.0123900.961562127349501.00.9615621.0
98571540022100.30569000.4639170.0123900.961562127349502.00.0959352.0
98571550022100.30569000.4639170.0123900.961562127349503.00.0123902.0
98571560022100.30569000.4639170.0123900.961562127349504.00.7350421.0
..........................................
383574974422100.61494500.3474720.2408550.86019732824805402634.00.2408552.0
383574984422100.61494500.3474720.2408550.86019732824805402636.00.2408552.0
383574994422100.61494500.3474720.2408550.86019732824805402638.00.2408552.0
383575004422100.61494500.3474720.2408550.86019732824805402640.00.2408552.0
383575014422100.61494500.3474720.2408550.86019732824805402642.00.2408552.0
\n", + "

18267295 rows × 13 columns

\n", + "
" + ], + "text/plain": [ + " RunId iteration Step n prob_notcontaminated contaminated \n", + "9857152 0 0 22 10 0.305690 0 \\\n", + "9857153 0 0 22 10 0.305690 0 \n", + "9857154 0 0 22 10 0.305690 0 \n", + "9857155 0 0 22 10 0.305690 0 \n", + "9857156 0 0 22 10 0.305690 0 \n", + "... ... ... ... .. ... ... \n", + "38357497 4 4 22 10 0.614945 0 \n", + "38357498 4 4 22 10 0.614945 0 \n", + "38357499 4 4 22 10 0.614945 0 \n", + "38357500 4 4 22 10 0.614945 0 \n", + "38357501 4 4 22 10 0.614945 0 \n", + "\n", + " average_risk_level min_risk_level max_risk_level num_agents \n", + "9857152 0.463917 0.012390 0.961562 12734950 \\\n", + "9857153 0.463917 0.012390 0.961562 12734950 \n", + "9857154 0.463917 0.012390 0.961562 12734950 \n", + "9857155 0.463917 0.012390 0.961562 12734950 \n", + "9857156 0.463917 0.012390 0.961562 12734950 \n", + "... ... ... ... ... \n", + "38357497 0.347472 0.240855 0.860197 3282480 \n", + "38357498 0.347472 0.240855 0.860197 3282480 \n", + "38357499 0.347472 0.240855 0.860197 3282480 \n", + "38357500 0.347472 0.240855 0.860197 3282480 \n", + "38357501 0.347472 0.240855 0.860197 3282480 \n", + "\n", + " AgentID risk_level payoff \n", + "9857152 0.0 0.818438 1.0 \n", + "9857153 1.0 0.961562 1.0 \n", + "9857154 2.0 0.095935 2.0 \n", + "9857155 3.0 0.012390 2.0 \n", + "9857156 4.0 0.735042 1.0 \n", + "... ... ... ... \n", + "38357497 5402634.0 0.240855 2.0 \n", + "38357498 5402636.0 0.240855 2.0 \n", + "38357499 5402638.0 0.240855 2.0 \n", + "38357500 5402640.0 0.240855 2.0 \n", + "38357501 5402642.0 0.240855 2.0 \n", + "\n", + "[18267295 rows x 13 columns]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# get data from the last step of each run\n", + "last_step = df[df.Step == 22]\n", + "last_step" + ] + }, + { + "cell_type": "markdown", + "id": "6527d658-8724-425f-ab3b-c5feb5b767c3", + "metadata": {}, + "source": [ + "## overall risk distribution at end of simulations" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "927567b5-bb71-41ba-9078-65631b9f82a3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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jcq+/OUvV3nl9evOO+/Mzj333OzWW2/NIiJbtGjRdvdfs2ZNNmzYsGz27NnZY489ln3/+9/PGhoasrvvvjvX/dZNaBx11FHZrFmzej/u6urKRo8enV100UVb3f+DH/xgNm3atM22HX300dlnPvOZpOscSPLO/H9t2rQpa2pqyn7605+mWuKAU8nMN23alL397W/PfvzjH2czZ84UGhXIO/crr7wyO/DAA7NXX321WksccPLOfNasWdk73/nOzbbNnj07O+aYY5Kuc6DakdD42te+lh166KGbbTv99NOzqVOn5rqvuvjRSc/b00+ePLl3W19vT//AAw9stn9ExNSpU7e5P5urZOb/q7OzM8rlcowYMSLVMgeUSmf+7W9/O0aOHBmf+MQnqrHMAaeSuf/iF7+ISZMmxaxZs2LvvfeO8ePHx3e+853o6uqq1rLrWiUzf/vb3x4rVqzo/fHKmjVr4s4774yTTz65KmveFfXX99Hk797aHyp5e/q1a9dudf+1a9cmW+dAUsnM/9fXv/71GD169BYnKltXyczvu+++uPbaa2PlypVVWOHAVMnc16xZE7/+9a/jwx/+cNx5552xevXq+PznPx/lcjnmzJlTjWXXtUpmPmPGjFi3bl0ce+yxkWVZbNq0KT772c/GN77xjWoseZe0re+jHR0d8a9//St22223HTpOXVzRoP7MmzcvFi5cGIsWLYqhQ4fWejkD0vr16+OMM86Ia665JlpaWmq9nF1Kd3d3jBw5Mn70ox/FkUceGaeffnqce+65cdVVV9V6aQPWsmXL4jvf+U788Ic/jN///vdx6623xh133BHnn39+rZdGH+riikYlb0+/zz775NqfzVUy8x6XXHJJzJs3L371q1/FhAkTUi5zQMk78yeffDKefvrpmD59eu+27u7uiIgYPHhwPPHEEzF27Ni0ix4AKjnXR40aFY2NjdHQ0NC77Y1vfGOsXbs2Xn311RgyZEjSNde7SmZ+3nnnxRlnnBGf/OQnIyLisMMOi40bN8anP/3pOPfcc2PQIP9v7m/b+j7a3Ny8w1czIurkikYlb08/adKkzfaPiFi6dKm3s99Blcw8IuLiiy+O888/P+6+++6YOHFiNZY6YOSd+SGHHBKrVq2KlStX9v455ZRT4sQTT4yVK1dGa2trNZdftyo514855phYvXp1b9hFRPz5z3+OUaNGiYwdUMnMOzs7t4iJntDLvGVXEv32fTTf81RrZ+HChVmxWMzmz5+fPfbYY9mnP/3pbPjw4dnatWuzLMuyM844Izv77LN797///vuzwYMHZ5dcckn2+OOPZ3PmzPHy1pzyznzevHnZkCFDsltuuSV7/vnne/+sX7++Vp9C3ck78//lVSeVyTv3Z555JmtqasrOOuus7Iknnshuv/32bOTIkdkFF1xQq0+h7uSd+Zw5c7KmpqbsxhtvzNasWZMtWbIkGzt2bPbBD36wVp9C3Vm/fn32yCOPZI888kgWEdlll12WPfLII9lf//rXLMuy7Oyzz87OOOOM3v17Xt761a9+NXv88cezK664YmC/vDXLsuz73/9+NmbMmGzIkCHZUUcdlT344IO9f3f88cdnM2fO3Gz/n/3sZ9m4ceOyIUOGZIceemh2xx13VHnF9S/PzPfbb78sIrb4M2fOnOovvI7lPc//m9CoXN65//a3v82OPvrorFgsZgceeGB24YUXZps2baryqutbnpmXy+Wsra0tGzt2bDZ06NCstbU1+/znP5/985//rP7C69RvfvObrT5G98x55syZ2fHHH7/FbY444ohsyJAh2YEHHphdd911ue/X28QDAMnUxXM0AID6JDQAgGSEBgCQjNAAAJIRGgBAMkIDAEhGaAAAyQgNACAZoQEAJCM0AIBkhAYAkIzQAACS+X+0MMTayYhK7AAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# overall ending risk distribution across all runs\n", + "last_step.risk_level.hist()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "3adaf26c-4d0e-4fae-9cca-649b6bd6286e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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n3++nVJtcblcHMghRs97Bn0PPXoeevQ89DrruZ0Vkxa2LoiNjY3V5MmTVV5e3jrm9XpVXl6u9PT0DrdJT09vM1+SysrKfM4HAAC9m+23dXJycrRkyRJNmTJFU6dO1YYNG9TY2KilS5dKkhYvXqxBgwapsLBQknTzzTdrxowZWrdunTIzM7Vlyxbt3btXmzdvDuwjAQAAEcF2OLnhhhv0xRdf6K677lJtba0uu+wybd++vfWi15qaGkVF/XdBZvr06SouLtYdd9yhVatW6ZJLLlFJSYnS0tIC9yj85HQ6tXr16nZvISF46HnPoO+hR89Dj56HXrB67rDsfr4HAAAgiPhuHQAAYBTCCQAAMArhBAAAGIVwAgAAjBLx4WTjxo0aOnSo4uLiNG3aNL377rudzt+6datGjRqluLg4jRs3TqWlpSGqNHLY6fnjjz+uq666ShdccIEuuOACzZ49u8vnCB2ze6y32LJlixwOh7KysoJbYASy2/OTJ08qOztbycnJcjqdGjlyJOcYm+z2fMOGDbr00kvVt29fpaam6pZbbtFXX30VomrD365duzR//nylpKTI4XB0+r14LSoqKjRp0iQ5nU6NGDFCRUVF9u/YimBbtmyxYmNjraeeesp6//33reXLl1vnn3++VVdX1+H8t99+24qOjrbuu+8+64MPPrDuuOMOKyYmxtq/f3+IKw9fdnu+aNEia+PGjda+ffusDz/80Prxj39s9evXz/r3v/8d4srDm92+tzhy5Ig1aNAg66qrrrKuv/760BQbIez23O12W1OmTLGuvfZaa/fu3daRI0esiooKq7q6OsSVhy+7PX/++ectp9NpPf/889aRI0esHTt2WMnJydYtt9wS4srDV2lpqXX77bdbr7zyiiXJ2rZtW6fzDx8+bMXHx1s5OTnWBx98YD300ENWdHS0tX37dlv3G9HhZOrUqVZ2dnbr7ebmZislJcUqLCzscP4PfvADKzMzs83YtGnTrJ/97GdBrTOS2O35N509e9ZKSEiwnnnmmWCVGJH86fvZs2et6dOnW0888YS1ZMkSwolNdnv+6KOPWhdffLF15syZUJUYcez2PDs727r66qvbjOXk5FhXXnllUOuMVN0JJytXrrTGjh3bZuyGG26wMjIybN1XxL6tc+bMGVVVVWn27NmtY1FRUZo9e7YqKys73KaysrLNfEnKyMjwOR9t+dPzb2pqapLH41H//v2DVWbE8bfvd999txITE3XTTTeFosyI4k/P//SnPyk9PV3Z2dkaOHCg0tLSdO+996q5uTlUZYc1f3o+ffp0VVVVtb71c/jwYZWWluraa68NSc29UaB+jxr5rcSBcPz4cTU3N7f+5doWAwcO1EcffdThNrW1tR3Or62tDVqdkcSfnn/T7373O6WkpLQ7uOGbP33fvXu3nnzySVVXV4egwsjjT88PHz6sN998Uz/84Q9VWlqqQ4cO6Ze//KU8Ho9Wr14dirLDmj89X7RokY4fP65vf/vbsixLZ8+e1c9//nOtWrUqFCX3Sr5+jzY0NOj//u//1Ldv327tJ2JXThB+1qxZoy1btmjbtm2Ki4vr6XIi1qlTp3TjjTfq8ccf14ABA3q6nF7D6/UqMTFRmzdv1uTJk3XDDTfo9ttv16ZNm3q6tIhVUVGhe++9V4888ojee+89vfLKK3rttddUUFDQ06WhCxG7cjJgwABFR0errq6uzXhdXZ2SkpI63CYpKcnWfLTlT89brF27VmvWrNEbb7yh8ePHB7PMiGO37//85z/1ySefaP78+a1jXq9XktSnTx8dPHhQw4cPD27RYc6fYz05OVkxMTGKjo5uHRs9erRqa2t15swZxcbGBrXmcOdPz++8807deOONWrZsmSRp3Lhxamxs1E9/+lPdfvvtbb4HDoHh6/eoy+Xq9qqJFMErJ7GxsZo8ebLKy8tbx7xer8rLy5Went7hNunp6W3mS1JZWZnP+WjLn55L0n333aeCggJt375dU6ZMCUWpEcVu30eNGqX9+/erurq69ee6667TrFmzVF1drdTU1FCWH5b8OdavvPJKHTp0qDUIStI//vEPJScnE0y6wZ+eNzU1tQsgLeHQ4mvlgiJgv0ftXasbXrZs2WI5nU6rqKjI+uCDD6yf/vSn1vnnn2/V1tZalmVZN954o3Xbbbe1zn/77betPn36WGvXrrU+/PBDa/Xq1XyU2Ca7PV+zZo0VGxtrvfzyy9axY8daf06dOtVTDyEs2e37N/FpHfvs9rympsZKSEiwVqxYYR08eNB69dVXrcTEROv3v/99Tz2EsGO356tXr7YSEhKsF154wTp8+LC1c+dOa/jw4dYPfvCDnnoIYefUqVPWvn37rH379lmSrAceeMDat2+fdfToUcuyLOu2226zbrzxxtb5LR8lvvXWW60PP/zQ2rhxIx8l7shDDz1kDRkyxIqNjbWmTp1qvfPOO63/NmPGDGvJkiVt5r/00kvWyJEjrdjYWGvs2LHWa6+9FuKKw5+dnl900UWWpHY/q1evDn3hYc7usf6/CCf+sdvzPXv2WNOmTbOcTqd18cUXW/fcc4919uzZEFcd3uz03OPxWHl5edbw4cOtuLg4KzU11frlL39pffnll6EvPEz95S9/6fAc3dLnJUuWWDNmzGi3zWWXXWbFxsZaF198sfX000/bvl+HZbG2BQAAzBGx15wAAIDwRDgBAABGIZwAAACjEE4AAIBRCCcAAMAohBMAAGAUwgkAADAK4QQAABiFcAIAAIxCOAEAAEYhnAAAAKMQTgAAgFH+H5t33StUTmoFAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# does it look any different if we change the number of bins?\n", + "last_step.risk_level.hist(bins=20)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "c26ce680-6892-42cc-80ea-20cf2361c9af", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0, 1, 2, 3, 4])" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# how many runs did we have?\n", + "# only 5, no variation in parameters\n", + "last_step.RunId.unique()" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "b8a09aaa-3e24-4881-a59a-941b00be1319", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABbYAAAM8CAYAAACLZPZlAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAABxJUlEQVR4nOzdf5RdZX0v/vdkGCZECT+MEBKDicgPFUgQSpqvWqFNCDQXze1SEVqJqdJbJS2aqxQskESsIAoGr9EsUQx0NUKxAtdigZg2cGkiLMAsxSqKgighAaTJmASHgTnfP1gZmUkGcnbOmZln8nqtdVbW2efZez7nc/ae55z37OzTUqvVagEAAAAAgEKMGOwCAAAAAACgHoJtAAAAAACKItgGAAAAAKAogm0AAAAAAIoi2AYAAAAAoCiCbQAAAAAAiiLYBgAAAACgKIJtAAAAAACKItgGAAAAAKAogm0AAAAAAIoy7ILtO++8M6eeemrGjRuXlpaW3HTTTXVvo1ar5XOf+1wOO+ywtLe3Z/z48fmHf/iHxhcLAAAAAEDd9hjsAhpty5YtmTx5cv7yL/8yf/Znf1ZpG+ecc05uv/32fO5zn8tRRx2Vp59+Ok8//XSDKwUAAAAAoIqWWq1WG+wimqWlpSU33nhjZs+e3bOss7Mzf//3f59vfOMb2bhxY4488sh85jOfyQknnJAk+fGPf5yjjz46DzzwQA4//PDBKRwAAAAAgH4Nu0uRvJx58+ZlzZo1ue666/KDH/wg7373u3PyySfnZz/7WZLk29/+dl73utflX//1XzNp0qRMnDgxH/zgB52xDQAAAAAwROxWwfajjz6ar3/967nhhhvytre9LYccckg+9rGP5a1vfWu+/vWvJ0l+8Ytf5Je//GVuuOGGXHvttVm2bFnuu+++vOtd7xrk6gEAAAAASIbhNbZfyg9/+MM8//zzOeyww3ot7+zszKte9aokSXd3dzo7O3Pttdf2jPva176WY489Ng8++KDLkwAAAAAADLLdKtjevHlzWltbc99996W1tbXXY6985SuTJAcddFD22GOPXuH3G97whiQvnPEt2AYAAAAAGFy7VbB9zDHH5Pnnn88TTzyRt73tbTsc85a3vCXPPfdcfv7zn+eQQw5Jkvz0pz9Nkrz2ta8dsFoBAAAAANixllqtVhvsIhpp8+bNeeihh5K8EGRfccUVOfHEE7P//vvn4IMPzl/8xV/kP//zP3P55ZfnmGOOyZNPPpmVK1fm6KOPzqxZs9Ld3Z0/+IM/yCtf+cosXrw43d3dOfvsszN69Ojcfvvtg/zsAAAAAAAYdsH2qlWrcuKJJ263fM6cOVm2bFm6urryqU99Ktdee20ee+yxjBkzJn/4h3+YRYsW5aijjkqSrFu3Ln/zN3+T22+/Pa94xStyyimn5PLLL8/+++8/0E8HAAAAAIA+hl2wDQAAAADA8DZisAsAAAAAAIB6DIsvj+zu7s66deuy9957p6WlZbDLAWAYqtVq+e1vf5tx48ZlxAh/F67KnA1AM5mvG8N8DUAzNWq+HhbB9rp16zJhwoTBLgOA3cCvfvWrvOY1rxnsMoplzgZgIJivd435GoCBsKvz9bAItvfee+8kLzRj9OjRO71eV1dXbr/99px00klpa2trVnnDkt5Vo2/V6V11eldN3751dHRkwoQJPXMO1VSds/uyX1end9XoW3V6V42+VWO+boxGzde7quTjoNTaS607Kbf2UutO1D4YSq076V37M88805D5elgE29v+a9To0aPrDrZHjRqV0aNHF7czDDa9q0bfqtO76vSumv765r/j7pqqc3Zf9uvq9K4afatO76rRt11jvt41jZqvd1XJx0GptZdad1Ju7aXWnah9MJRad7Lj2nd1vnbRMQAAAAAAiiLYBgAAAACgKIJtAAAAAACKItgGAAAAAKAogm0AAAAAAIoi2AYAAAAAoCiCbQAAAAAAiiLYBgAAAACgKIJtAAAAAACKItgGAAAAAKAogm0AAAAAAIoi2AYAAAAAoCiCbQAAAAAAiiLYBgAAAACgKHsMdgEAVDPxvFt2alx7ay2XHZ8cufC2dD7f0tSaHrl0VlO3DwAAAJA4YxsAAAAAgMIItgEAAAAAKIpgGwAAAACAorjG9g7s7HVrB5Lr1gLA9gbi2vH1MmcDAAA0nzO2AQAAAAAoimAbAAAAAICiCLYBAAAAACiKYBsAAAAAgKIItgEAAAAAKIpgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAACiKYBsAAAAAgKIItgEAAAAAKEpdwfYll1ySP/iDP8jee++dAw44ILNnz86DDz74suvdcMMNOeKIIzJy5MgcddRR+c53vtPr8VqtlosuuigHHXRQ9tprr0yfPj0/+9nP6nsmAAAAAADsFuoKtu+4446cffbZ+d73vpcVK1akq6srJ510UrZs2dLvOqtXr87pp5+eD3zgA/n+97+f2bNnZ/bs2XnggQd6xlx22WX5whe+kKVLl+buu+/OK17xisycOTO/+93vqj8zAAAAAACGpT3qGXzrrbf2ur9s2bIccMABue+++/JHf/RHO1znyiuvzMknn5yPf/zjSZKLL744K1asyBe/+MUsXbo0tVotixcvzgUXXJB3vvOdSZJrr702Bx54YG666aa8973vrfK8AAAAAAAYpuoKtvvatGlTkmT//ffvd8yaNWsyf/78XstmzpyZm266KUny8MMPZ/369Zk+fXrP4/vss0+mTp2aNWvW7DDY7uzsTGdnZ8/9jo6OJElXV1e6urp2uv5tY/uu095a2+ltDJR6ntdA6K93vDR9q07vtrezv6vaR9R6/dtMw+n16bvPDafnNpAaNWf3tW3dgdiv6zXU9xX7dDX6Vp3eVaNv1ehXNc2ar3dVycdBqbWXWndSbu2l1p2ofTCUWnfSu/ZG1d9Sq9UqfSLs7u7OO97xjmzcuDF33XVXv+P23HPPXHPNNTn99NN7ln3pS1/KokWLsmHDhqxevTpvectbsm7duhx00EE9Y97znvekpaUl119//XbbXLhwYRYtWrTd8uXLl2fUqFFVng4AvKStW7fmjDPOyKZNmzJ69OjBLqcY5mwABpL5uhrzNQADqVHzdeUzts8+++w88MADLxlqN8v555/f6yzwjo6OTJgwISeddFJdzejq6sqKFSsyY8aMtLW19Sw/cuFtDa23ER5YOHOwS+ilv97x0vStOr3b3s7+rmofUcvFx3XnwntHpLO7pak1DbXfVbui7z637cwl6tOoObuvba/PQOzX9Rrqx4Hfp9XoW3V6V42+VWO+rqZZ8/WuKvk4KLX2UutOyq291LoTtQ+GUutOetf+zDPPNGSblYLtefPm5V//9V9z55135jWvec1Ljh07dmw2bNjQa9mGDRsyduzYnse3LXvxGdsbNmzIlClTdrjN9vb2tLe3b7e8ra2t0ovad73O54fWB+QkQ3Znrdrz3Z2+Vad3v1fv76rO7pam/34bjq/Ntn1uOD63gdDoObuvgdiv61XKvmK/rkbfqtO7avStPnpVTbPn6101VOqootTaS607Kbf2UutO1D4YSq07eaH25557riHbGlHP4Fqtlnnz5uXGG2/Mv//7v2fSpEkvu860adOycuXKXstWrFiRadOmJUkmTZqUsWPH9hrT0dGRu+++u2cMAAAAAABsU9cZ22effXaWL1+em2++OXvvvXfWr1+f5IUve9xrr72SJGeeeWbGjx+fSy65JElyzjnn5O1vf3suv/zyzJo1K9ddd13uvffefOUrX0mStLS05CMf+Ug+9alP5dBDD82kSZNy4YUXZty4cZk9e3YDnyoAAAAAAMNBXcH2l7/85STJCSec0Gv517/+9bz//e9Pkjz66KMZMeL3J4L/f//f/5fly5fnggsuyCc+8Ykceuihuemmm3LkkUf2jDn33HOzZcuW/NVf/VU2btyYt771rbn11lszcuTIik8LAAAAAIDhqq5gu1arveyYVatWbbfs3e9+d9797nf3u05LS0s++clP5pOf/GQ95QAAAAAAsBuq6xrbAAAAAAAw2ATbAAAAAAAURbANAAAAAEBRBNsAAAAAABRFsA0AAAAAQFEE2wAAAAAAFEWwDQAAAABAUQTbAAAAAAAURbANAAAAAEBRBNsAAAAAABRlj8EuAODFJp53yw6Xt7fWctnxyZELb0vn8y0DXBUAAAAAQ4kztgEAAAAAKIpgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAACiKYBsAAAAAgKIItgEAAAAAKIpgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAACiKYBsAAAAAgKIItgEAAAAAKIpgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAAChK3cH2nXfemVNPPTXjxo1LS0tLbrrpppcc//73vz8tLS3b3d70pjf1jFm4cOF2jx9xxBF1PxkAAAAAAIa/uoPtLVu2ZPLkyVmyZMlOjb/yyivz+OOP99x+9atfZf/998+73/3uXuPe9KY39Rp311131VsaAAAAAAC7gT3qXeGUU07JKaecstPj99lnn+yzzz4992+66ab893//d+bOndu7kD32yNixY+stBwAAAACA3Uzdwfau+trXvpbp06fnta99ba/lP/vZzzJu3LiMHDky06ZNyyWXXJKDDz54h9vo7OxMZ2dnz/2Ojo4kSVdXV7q6una6lm1j+67T3lrb6W0MlHqe10Dor3e8NH17ef0df+0jar3+ZecNZO+G077d93gd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2y1va2ur9KL2Xa/z+aH1ATnJkN1Zq/Z8d6dv/Xu546+zu2VIHqMlGIjeDcf9etvxOhyf20Bo9Jzd11D8nVDKvmK/rkbfqtO7avStPnpVTbPn6101VOqootTaS607Kbf2UutO1D4YSq07eaH25557riHbGtGQrdRp7dq1Oeigg5Ike+65Z4499tisXLmy5/Hu7u6sXLmy1xncAAAAAACQVDhje/PmzXnooYd67j/88MNZu3Zt9t9//xx88ME5//zz89hjj+Xaa69NkixevDiTJk3Km970pvzud7/LV7/61fz7v/97br/99p5tzJ8/P3PmzMlxxx2X448/PosXL86WLVsyd+7cBjxFAAAAAACGk7qD7XvvvTcnnnhiz/358+cnSebMmZNly5bl8ccfz6OPPtrz+LPPPpv//b//dx577LGMGjUqRx99dL773e/22sZpp52WJ598MhdddFHWr1+fKVOm5NZbb82BBx64K88NAAAAAIBhqO5g+4QTTshLfd/ksmXLet0/99xzc+65577sdufNm5d58+bVWw4AAAAAALuZQbnGNgAAAAAAVCXYBgAAAACgKIJtAAAAAACKItgGAAAAAKAogm0AAAAAAIoi2AYAAAAAoCiCbQAAAAAAiiLYBgAAAACgKIJtAAAAAACKItgGAAAAAKAogm0AAAAAAIoi2AYAAAAAoCiCbQAAAAAAiiLYBgAAAACgKIJtAAAAAACKItgGAAAAAKAogm0AAAAAAIoi2AYAAAAAoCiCbQAAAAAAiiLYBgAAAACgKIJtAAAAAACKItgGAAAAAKAogm0AAAAAAIoi2AYAAAAAoCiCbQAAAAAAiiLYBgAAAACgKIJtAAAAAACKItgGAAAAAKAogm0AAAAAAIpSd7B955135tRTT824cePS0tKSm2666SXHf+tb38qMGTPy6le/OqNHj860adNy22239RqzcOHCtLS09LodccQR9ZYGAAAAAMBuoO5ge8uWLZk8eXKWLFmyU+PvvPPOzJgxI9/5zndy33335cQTT8ypp56a73//+73GvelNb8rjjz/ec7vrrrvqLQ0AAAAAgN3AHvWucMopp+SUU07Z6fGLFy/udf/Tn/50br755nz729/OMccc8/tC9tgjY8eO3altdnZ2prOzs+d+R0dHkqSrqytdXV07Xdu2sX3XaW+t7fQ2Bko9z2sg9Nc7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uHFj3vrWt+bWW2/NyJEjG/AUAQAAAAAYTuoOtpNk3rx5mTdv3g4fW7VqVa/7n//85/P5z3/+JbfX0tKST37yk/nkJz9ZpRwAAAAAAHYjdV1jGwAAAAAABptgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAACiKYBsAAAAAgKIItgEAAAAAKIpgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAACiKYBsAAAAAgKIItgEAAAAAKIpgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAACiKYBsAAAAAgKIItgEAAAAAKIpgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAACiKYBsAAAAAgKIItgEAAAAAKIpgGwAAAACAogi2AQAAAAAoimAbAAAAAICiVAq2lyxZkokTJ2bkyJGZOnVq7rnnnn7HXnXVVXnb296W/fbbL/vtt1+mT5++3fj3v//9aWlp6XU7+eSTq5QGAAAAAMAwV3ewff3112f+/PlZsGBB7r///kyePDkzZ87ME088scPxq1atyumnn57/+I//yJo1azJhwoScdNJJeeyxx3qNO/nkk/P444/33L7xjW9Ue0YAAAAAAAxre9S7whVXXJGzzjorc+fOTZIsXbo0t9xyS66++uqcd955243/p3/6p173v/rVr+Zf/uVfsnLlypx55pk9y9vb2zN27Nh6ywEAAACoy8Tzbmnq9ttba7ns+OTIhbel8/mWnVrnkUtnNbUmgOGmrmD72WefzX333Zfzzz+/Z9mIESMyffr0rFmzZqe2sXXr1nR1dWX//ffvtXzVqlU54IADst9+++WP//iP86lPfSqvetWrdriNzs7OdHZ29tzv6OhIknR1daWrq2unn8+2sX3XaW+t7fQ2Bko9z2sgbKunfYRe1aO/fY7f6+/427avDcV9bqjb3XtX9Xjre7w6bqtp1Jzd11Ceh4aiF/faPl2NvlWnd9XoWzX6VU2z5utd1czjoNmf+6u8Bx8K+2/Jv3tKrb3UuhO1D4ZS6056196o+ltqtdpO/5Zdt25dxo8fn9WrV2fatGk9y88999zccccdufvuu192Gx/+8Idz22235Uc/+lFGjhyZJLnuuusyatSoTJo0KT//+c/ziU98Iq985SuzZs2atLa2breNhQsXZtGiRdstX758eUaN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5uY2GLddna/r+sPNU089leeffz4HHnhgr+UHHnhgfvKTn+xwnfXr1+9w/Pr163se37asvzF9nX/++Zk/f37P/e7u7jz99NN51atelZaWnf9rRUdHRyZMmJBf/epXGT169E6vh95VpW/V6V11eldN377VarX89re/zbhx4wa7tKI0as7uy35dnd5Vo2/V6V01+laN+bqaZs3Xu6rk46DU2kutOym39lLrTtQ+GEqtO+ld+957792Q+bqZ/yOhadrb29Pe3t5r2b777lt5e6NHjy5uZxgq9K4afatO76rTu2pe3Ld99tlnkKspT6Pn7L7s19XpXTX6Vp3eVaNv9TNf16/Z8/WuKvk4KLX2UutOyq291LoTtQ+GUutOfl97I+brEfUMHjNmTFpbW7Nhw4Zeyzds2JCxY8fucJ2xY8e+5Pht/9azTQAAAAAAdl91Bdt77rlnjj322Kxc+fsL4nd3d2flypWZNm3aDteZNm1ar/FJsmLFip7xkyZNytixY3uN6ejoyN13393vNgEAAAAA2H3VfSmS+fPnZ86cOTnuuONy/PHHZ/HixdmyZUvmzp2bJDnzzDMzfvz4XHLJJUmSc845J29/+9tz+eWXZ9asWbnuuuty77335itf+UqSpKWlJR/5yEfyqU99KoceemgmTZqUCy+8MOPGjcvs2bMb90x3oL29PQsWLNjuv1zx8vSuGn2rTu+q07tq9G1o8/pUp3fV6Ft1eleNvkHZx0GptZdad1Ju7aXWnah9MJRad9Kc2ltqtVqt3pW++MUv5rOf/WzWr1+fKVOm5Atf+EKmTp2aJDnhhBMyceLELFu2rGf8DTfckAsuuCCPPPJIDj300Fx22WX50z/9057Ha7VaFixYkK985SvZuHFj3vrWt+ZLX/pSDjvssF1/hgAAAAAADCuVgm0AAAAAABgsdV1jGwAAAAAABptgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAACiKYBsAAAAAgKIItgEAAAAAKIpgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAACiKYBsAAAAAgKIItgEAAAAAKIpgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAACiKYBsAAAAAgKIItgEAAAAAKIpgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAACiKYBsA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4//pn/4pH/7whzNlypQcccQR+epXv5ru7u6sXLmyGeUBAAAAAFCwhn955LPPPpv77rsv559/fs+yESNGZPr06VmzZs1ObWPr1q3p6urK/vvvv8PHOzs709nZ2XO/o6MjSdLV1ZWurq6drnXb2HrWqaK9tdbU7Q+G9hEvPKdm9264Gah9bjjSu+r0rpq+fdO/aho1Z/fldamuEb1r9nubofi61tu33bFH/XG8VrPt/fa2f5thOL4mw/E5DYT+5msAGMpaarVaQ98prVu3LuPHj8/q1aszbdq0nuXnnntu7rjjjtx9990vu40Pf/jDue222/KjH/0oI0eO3O7xhQsXZtGiRdstX758eUaNGrVrTwAAdmDr1q0544wzsmnTpowePXqwyymGORuAgWS+rqa/+VofAWiGjo6O7LPPPrs8zwy5YPvSSy/NZZddllWrVuXoo4/e4Zgd/TV5woQJeeqpp+pqRldXV1asWJEZM2akra1tp9er15ELb2vatgdL+4haLj6uu+m9G24Gap8bjvSuOr2rpm/fOjo6MmbMGB/w6tSoObsv+3V1jehds9/bPLBwZlO3X0W9fdsde9Qfx2s1x37y1lx8XHcuvHdEOrtbmvIzStqPdpb5upr+5mt9BKAZGhVsN/xSJGPGjElra2s2bNjQa/mGDRsyduzYl1z3c5/7XC699NJ897vf7TfUTpL29va0t7dvt7ytra3Sm+Wq6+2szueb80Z0KGh274YrfatO76rTu2q29U3vqmn0nN2s7eyOdqV3zX5vM5Rf053t2+7co/44XuuzLczu7G5p2v40HF+P4ficBkJ/8zUADGUN//LIPffcM8cee2yvL37c9kWQLz6Du6/LLrssF198cW699dYcd9xxjS4LAAAAAIBhouFnbCfJ/PnzM2fOnBx33HE5/vjjs3jx4mzZsiVz585Nkpx55pkZP358LrnkkiTJZz7zmVx00UVZvnx5Jk6cmPXr1ydJXvnKV+aVr3xlM0oEAAAAAKBQTQm2TzvttDz55JO56KKLsn79+kyZMiW33nprDjzwwCTJo48+mhEjfn+y+Je//OU8++yzede73tVrOwsWLMjChQubUSIAAAAAAIVqSrCdJPPmzcu8efN2+NiqVat63X/kkUeaVQYAAAAAAMNMw6+xDQAAAAAAzSTYBgAAAACgKE27FAkAAEB/Jp53S1O3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YX8T6dtZqrmTkpKwbNkyAEBubi76+/tx4MAB3HfffbGMG0XnvpvJ9TIhJSUFa9asweDgYCwiTknnnpzJzK9cuYLGxkbs3bs3lhGnpWvPquRevHgxvvjiC/zzzz+4ePEiMjIyUFFRgezs7JjnnanZuMdO2Fc/drsda9euRVtbW+TY+Pg42tra4Ha7p3yO2+2OWg8AX3/99bTrE5XK7ADg9ddfx759+9DS0oJ169aZETWuGJ3b8uXL0dPTA5/PF3k8/PDDuP/+++Hz+ZCVlWVmfEupXHMbNmzA4OBg5IUVAPz8889wuVxz4oZ4gsrsrl69OqmUJ140/fc7Hmgq7AjzsMPVscPVsMPVscPVsL+J9O0s1dzXGh8fRygUikXEaencd7Mx97GxMfT09MDlcsUq5iQ69+RMZv7ZZ58hFArh0UcfjXXMKenaszOZucPhQGZmJsLhMI4dO4bi4uJYx52xWbmHM/SVlpppbGyU1NRU+eijj6Svr0927twpN910kwSDQRER8Xg8UlFREVn//fffS3JystTW1kp/f79UVVVJSkqK9PT0WHUKljE6u5qaGrHb7fL555/LuXPnIo/h4WGrTsESRud2LSu/YdpqRmf322+/idPplN27d8vAwIB89dVXkpaWJq+99ppVp2AZo7OrqqoSp9Mpn3zyiQQCAWltbZWcnBzZunWrVadgieHhYenu7pbu7m4BIAcPHpTu7m759ddfRUSkoqJCPB5PZH0gEJD58+fL888/L/39/XL48GGx2WzS0tJi1SkkNHa4Ona4Gna4Ona4GvY3kb6dZTR3dXW1tLa2it/vl76+PqmtrZXk5GQ5cuSIqblVsl/Lyr4zmv3VV1+VEydOiN/vl87OTtm2bZs4HA7p7e2N69zx1JOq18vGjRulpKTE7LhRdO1Zo7lPnz4tx44dE7/fL999951s3rxZ7rjjDvnzzz9NzS1izT12Qm9si4gcOnRIbr/9drHb7ZKXlyenT5+O/KygoEBKS0uj1n/66ady1113id1ul5UrV0pTU5PJieOHkdktXbpUAEx6VFVVmR/cYkavuf81l2+KRYzP7ocffpD169dLamqqZGdny/79+yUcDpucOj4Ymd3o6Ki88sorkpOTIw6HQ7KysmTXrl2WFJ+VTp48OeX/WxOzKi0tlYKCgknPyc3NFbvdLtnZ2dLQ0GB67rmEHa6OHa6GHa6OHa6G/U2kb2cZyV1ZWSnLli0Th8MhN998s7jdbmlsbDQ98wSd+85I9meffTayNj09XYqKiqSrq8uC1Hr3pNHsZ8+eFQDS2tpqctLJdO1ZI7nb29tlxYoVkpqaKrfeeqt4PB75448/TM8sYs099g0i/Ls1IiIiIiIiIiIiItJHwn7GNhERERERERERERElJm5sExEREREREREREZFWuLFNRERERERERERERFrhxjYRERERERERERERaYUb20RERERERERERESkFW5sExEREREREREREZFWuLFNRERERERERERERFrhxjYRERERERERERERaYUb20RERERERERERESkFW5sExEREREREREREZFWuLFNRERERERERERERFr5D6MB2v+gUHmIAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# plot a histogram of ending risk levels for each run\n", + "# combine into a grid of plots\n", + "import matplotlib.pyplot as plt\n", + "fig, ax = plt.subplots(ncols=3, nrows=2, sharex='col', sharey='row', figsize=(18,10))\n", + "\n", + "for run in last_step.RunId.unique():\n", + " run_last_step = last_step[last_step.RunId == run]\n", + " plot_location = ax[int(run/3), int(run % 3)]\n", + " run_last_step.risk_level.hist(ax=plot_location, bins=10)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 1c9b44dc3a3d709ca910a61676863470d867e1dd Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 19 Jul 2023 16:15:43 -0400 Subject: [PATCH 042/141] Add unit tests for batch run script --- simulatingrisk/batch_run.py | 4 +- tests/test_batch_run.py | 73 +++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 tests/test_batch_run.py diff --git a/simulatingrisk/batch_run.py b/simulatingrisk/batch_run.py index e3cd1a9..4d794c1 100755 --- a/simulatingrisk/batch_run.py +++ b/simulatingrisk/batch_run.py @@ -43,7 +43,7 @@ def riskyfood_batch_run(): return results -def save_results(simulation, data): +def save_results(simulation, results): # save as csv for external analysis # - use datetime to distinguish this run, but make nicer for filename datestr = datetime.today().isoformat().replace(".", "_").replace(":", "") @@ -57,6 +57,8 @@ def save_results(simulation, data): dict_writer.writeheader() dict_writer.writerows(results) + return output_filename + if __name__ == "__main__": parser = argparse.ArgumentParser( diff --git a/tests/test_batch_run.py b/tests/test_batch_run.py new file mode 100644 index 0000000..69f9a2a --- /dev/null +++ b/tests/test_batch_run.py @@ -0,0 +1,73 @@ +import csv +import os +from datetime import date +from unittest.mock import patch + +from simulatingrisk.batch_run import ( + riskybet_batch_run, + riskyfood_batch_run, + save_results, +) +from simulatingrisk.risky_bet.model import RiskyBetModel +from simulatingrisk.risky_food.model import RiskyFoodModel + + +# patch mesa.batch_run in context of local batch run script +@patch("simulatingrisk.batch_run.batch_run") +def test_riskybet_batch_run(mock_batch_run): + # assert mesa batch run is called as expected + results = riskybet_batch_run() + mock_batch_run.assert_called_with( + RiskyBetModel, + parameters={ + "grid_size": [10, 20, 30], # 100], + "risk_adjustment": ["adopt", "average"], + }, + iterations=5, + max_steps=100, + number_processes=1, # set None to use all available; set 1 for jupyter + data_collection_period=1, + display_progress=True, + ) + assert results == mock_batch_run.return_value + + +# patch mesa.batch_run in context of local batch run script +@patch("simulatingrisk.batch_run.batch_run") +def test_riskyfood_batch_run(mock_batch_run): + # assert mesa batch run is called as expected + results = riskyfood_batch_run() + mock_batch_run.assert_called_with( + RiskyFoodModel, + parameters={"n": 10}, + iterations=5, + max_steps=22, + number_processes=1, # set None to use all available; set 1 for jupyter + data_collection_period=1, + display_progress=True, + ) + assert results == mock_batch_run.return_value + + +def test_save_results(capsys, tmpdir): + # output is saved to current working directory; + # change working directory to tmpdir + os.chdir(tmpdir) + mock_data = [{"a": 1, "b": 2}, {"a": 10, "b": 20, "c": 3}] + outfile = save_results("simulationfoo", mock_data) + # filename includes simulation name, date/time and csv extension + assert outfile.startswith("simulationfoo_") + assert date.today().isoformat() in outfile + assert outfile.endswith(".csv") + + captured = capsys.readouterr() + assert captured.out == ("Saving data collection results to: %s\n" % outfile) + + with open(outfile) as testcsv: + csvreader = csv.DictReader(testcsv) + rows = list(csvreader) + # should include keys from last rof of data even if not present in first + assert set(rows[0].keys()) == {"a", "b", "c"} + # spot check some data + assert rows[0]["a"] == "1" # string because read from file + assert rows[1]["b"] == "20" From b7b4fcc1e7b7fda1ba2633f4449153a8bca48342 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 25 Jul 2023 13:54:52 -0400 Subject: [PATCH 043/141] Implement risky food types variant #5 --- simulatingrisk/risky_food/model.py | 128 ++++++++++++++++++++++++----- simulatingrisk/risky_food/run.py | 12 ++- tests/test_risky_food.py | 22 ++++- 3 files changed, 140 insertions(+), 22 deletions(-) diff --git a/simulatingrisk/risky_food/model.py b/simulatingrisk/risky_food/model.py index 7cad45e..e16d7fb 100644 --- a/simulatingrisk/risky_food/model.py +++ b/simulatingrisk/risky_food/model.py @@ -1,3 +1,4 @@ +from collections import defaultdict from enum import Enum from functools import cached_property from statistics import mean @@ -21,7 +22,12 @@ class Agent(mesa.Agent): def __init__(self, unique_id, model, risk_level=None): super().__init__(unique_id, model) # get a random risk tolerance; returns a value between 0.0 and 1.0 - self.risk_level = risk_level or self.random.random() + if risk_level is None: # only set randomly if None; allow zero risk + risk_level = self.random.random() + self.risk_level = risk_level + + def __repr__(self): + return f"" def step(self): # choose food based on the probability not contaminated and risk tolerance @@ -36,25 +42,50 @@ class RiskyFoodModel(mesa.Model): prob_notcontaminated = None running = True # required for batch running - def __init__(self, n): + def __init__(self, n=110, mode="types"): self.num_agents = n + self.mode = mode self.schedule = mesa.time.SimultaneousActivation(self) # initialize agents for the first round - for i in range(self.num_agents): - a = Agent(i, self) - self.schedule.add(a) - self.nextid = i + 1 + # when mode is types, initialize 10 agents each of 11 risk types + if mode == "types": + # currently ignores n... + # maybe n could be n per type when mode is types + for i in range(11): + risk_level = i / 10 # 0, 0.1, 0.2, 0.3, ... 1.0 + for j in range(10): + # agents need unique ids; + # create them from type & index + agent_id = f"{i}-{j}" + a = Agent(agent_id, self, risk_level=risk_level) + self.schedule.add(a) + + else: + # when mode is not types, initialize risk level randomly + for i in range(self.num_agents): + a = Agent(i, self) + self.schedule.add(a) + + self.nextid = self.num_agents + 1 + + model_data = { + "prob_notcontaminated": "prob_notcontaminated", + "contaminated": "contaminated", + "average_risk_level": "avg_risk_level", + "min_risk_level": "min_risk_level", + "max_risk_level": "max_risk_level", + "num_agents": "total_agents", + } + # # report percent agents by risk level + # for i in range(11): + # risk_level = i / 10 + # model_data["pct_r%.1f" % risk_level] = lambda m: m.percent_agents_risk( + # risk_level + # ) self.datacollector = mesa.DataCollector( - model_reporters={ - "prob_notcontaminated": "prob_notcontaminated", - "contaminated": "contaminated", - "average_risk_level": "avg_risk_level", - "min_risk_level": "min_risk_level", - "max_risk_level": "max_risk_level", - "num_agents": "total_agents", - }, + model_reporters=model_data, agent_reporters={"risk_level": "risk_level", "payoff": "payoff"}, ) @@ -88,6 +119,13 @@ def get_risky_food_status(self): def propagate(self): # update agents based on payoff from the completed round + # when mode is types, payoff is based on number of each type of agent + if self.mode == "types": + self.propagate_types() + return + + # otherwise, use previous logic + # get a generator of agents from the scheduler that # will allow us to add and remove for agent in self.schedule.agent_buffer(): @@ -95,10 +133,27 @@ def propagate(self): # logic is offspring = to payoff, original dies off, # but for efficiency just add payoff - 1 and keep the original for i in range(agent.payoff - 1): - a = Agent(i + self.nextid, self, agent.risk_level) + a = Agent(i + self.nextid, self, risk_level=agent.risk_level) self.schedule.add(a) - self.nextid += agent.payoff + self.nextid = self.total_agents + 1 + + def propagate_types(self): + for risk_level, agents in self.agents_by_risktype.items(): + # adjust population based on payoff and number of agents + total = len(agents) + # calculate number of agents of this type for next round + new_total = int((total * agents[0].payoff) / 2) + + # if new total is less, remove agents over the needed total + for agent in agents[new_total:]: + self.schedule.remove(agent) + # if new total is more, add new agents with same risk level + if new_total > total: + for i in range(new_total - total): + a = Agent(self.nextid, self, risk_level) + self.schedule.add(a) + self.nextid += 1 @property def contaminated(self): @@ -119,6 +174,33 @@ def agents(self): def total_agents(self): return self.schedule.get_agent_count() + def total_agents_risk(self, risk_level): + # total number of agents with a particular risk level + return len(self.agents_by_risktype[risk_level]) + # return len([a for a in self.agents if a.risk_level == risk_level]) + + def percent_agents_risk(self, risk_level): + # # percent of agents with a particular risk level + risk_total = self.total_agents_risk(risk_level) + # print( + # "risk %s total %s total agents %d percent %s" + # % ( + # risk_level, + # risk_total, + # self.total_agents, + # (risk_level / self.total_agents) * 100, + # ) + # ) + return (risk_total / self.total_agents) * 100 + + @property + def agents_by_risktype(self): + # group agents by risk level for propagation + agents = defaultdict(list) + for a in self.agents: + agents[a.risk_level].append(a) + return agents + @cached_property def agent_risk_levels(self) -> [float]: # list of all risk levels for all current agents; @@ -139,16 +221,24 @@ def min_risk_level(self): def max_risk_level(self): return max(self.agent_risk_levels) + payoffs = { + "range": {"safe": 2, "not_contaminated": 3, "contaminated": 1}, + "types": {"safe": 2, "not_contaminated": 4, "contaminated": 1}, + } + def payoff(self, choice): "Calculate the payoff for a given choice, based on current food status" # safe food choice always has a payoff of 2 if choice == FoodChoice.SAFE: - return 2 + # return 2 + return self.payoffs[self.mode]["safe"] # payoff for risky food choice depends on contamination # - if not contaminated, payoff of 3 if self.risky_food_status == FoodStatus.NOTCONTAMINATED: - return 3 + # return 3 + return self.payoffs[self.mode]["not_contaminated"] # otherwise only payoff of 1 - return 1 + return self.payoffs[self.mode]["contaminated"] + # return 1 diff --git a/simulatingrisk/risky_food/run.py b/simulatingrisk/risky_food/run.py index 35d6610..6184d10 100644 --- a/simulatingrisk/risky_food/run.py +++ b/simulatingrisk/risky_food/run.py @@ -2,12 +2,14 @@ from simulatingrisk.risky_food.model import RiskyFoodModel + chart = mesa.visualization.ChartModule( [ {"Label": "prob_notcontaminated", "Color": "blue"}, {"Label": "contaminated", "Color": "red"}, ], data_collector_name="datacollector", + canvas_height=100, # default height is 200 ) risk_chart = mesa.visualization.ChartModule( [ @@ -16,17 +18,23 @@ {"Label": "max_risk_level", "Color": "orange"}, ], data_collector_name="datacollector", + canvas_height=100, ) -agent_chart = mesa.visualization.ChartModule( +total_agent_chart = mesa.visualization.ChartModule( [ {"Label": "num_agents", "Color": "gray"}, ], data_collector_name="datacollector", + canvas_height=100, ) server = mesa.visualization.ModularServer( - RiskyFoodModel, [chart, risk_chart, agent_chart], "Risky Food", {"n": 20} + RiskyFoodModel, + # [chart, risk_chart, agent_risk_chart, total_agent_chart], + [chart, risk_chart, total_agent_chart], + "Risky Food", + {"n": 20, "mode": "types"}, ) server.port = 8521 # The default server.launch() diff --git a/tests/test_risky_food.py b/tests/test_risky_food.py index d65eab0..83a464a 100644 --- a/tests/test_risky_food.py +++ b/tests/test_risky_food.py @@ -1,8 +1,9 @@ from collections import Counter import math + import pytest -from simulatingrisk.risky_food.model import RiskyFoodModel, FoodStatus +from simulatingrisk.risky_food.model import RiskyFoodModel, FoodStatus, Agent test_probabilities = [ @@ -37,3 +38,22 @@ def test_risky_food_status(prob_notcontaminated): assert math.isclose( result_count[FoodStatus.NOTCONTAMINATED], expected, abs_tol=total_runs * 0.1 ) + + +def test_agent_init(): + model = RiskyFoodModel(1) + agent_id = 123 + agent = Agent(agent_id, model) + assert agent.model == model + assert agent.unique_id == agent_id + # random risk level + assert agent.risk_level >= 0.0 and agent.risk_level <= 1.0 + + # assigned risk level + risk = 0.4 + agent2 = Agent(1, model, risk_level=risk) + assert agent2.risk_level == risk + + # allow zero risk (should not get a random value) + agent0 = Agent(1, model, risk_level=0) + assert agent0.risk_level == 0 From dffb4470dbb6d679710642694e98620ddef30169 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 25 Jul 2023 14:21:19 -0400 Subject: [PATCH 044/141] Split out server setup from server running code; add histogram #10 --- simulatingrisk/risky_food/HistogramModule.js | 51 +++++++++++++++ simulatingrisk/risky_food/run.py | 32 ++------- simulatingrisk/risky_food/server.py | 68 ++++++++++++++++++++ 3 files changed, 125 insertions(+), 26 deletions(-) create mode 100644 simulatingrisk/risky_food/HistogramModule.js create mode 100644 simulatingrisk/risky_food/server.py diff --git a/simulatingrisk/risky_food/HistogramModule.js b/simulatingrisk/risky_food/HistogramModule.js new file mode 100644 index 0000000..c46ee63 --- /dev/null +++ b/simulatingrisk/risky_food/HistogramModule.js @@ -0,0 +1,51 @@ +const HistogramModule = function(bins, canvas_width, canvas_height, label) { + // Create the canvas object: + const canvas = document.createElement("canvas"); + Object.assign(canvas, { + width: canvas_width, + height: canvas_height, + style: "border:1px dotted", + }); + // Append it to #elements: + const elements = document.getElementById("elements"); + elements.appendChild(canvas); + + // Create the context and the drawing controller: + const context = canvas.getContext("2d"); + + // Prep the chart properties and series: + const datasets = [{ + label: label, + fillColor: "rgba(151,187,205,0.5)", + strokeColor: "rgba(151,187,205,0.8)", + highlightFill: "rgba(151,187,205,0.75)", + highlightStroke: "rgba(151,187,205,1)", + data: [] + }]; + + // Add a zero value for each bin + for (var i in bins) + datasets[0].data.push(0); + + const data = { + labels: bins, + datasets: datasets + }; + + const options = { + scaleBeginsAtZero: true + }; + + // Create the chart object + let chart = new Chart(context, {type: 'bar', data: data, options: options}); + +this.render = function(data) { + datasets[0].data = data; + chart.update(); + }; + + this.reset = function() { + chart.destroy(); + chart = new Chart(context, {type: 'bar', data: data, options: options}); + }; +}; diff --git a/simulatingrisk/risky_food/run.py b/simulatingrisk/risky_food/run.py index 6184d10..c75f7f8 100644 --- a/simulatingrisk/risky_food/run.py +++ b/simulatingrisk/risky_food/run.py @@ -1,38 +1,18 @@ import mesa from simulatingrisk.risky_food.model import RiskyFoodModel - - -chart = mesa.visualization.ChartModule( - [ - {"Label": "prob_notcontaminated", "Color": "blue"}, - {"Label": "contaminated", "Color": "red"}, - ], - data_collector_name="datacollector", - canvas_height=100, # default height is 200 -) -risk_chart = mesa.visualization.ChartModule( - [ - {"Label": "average_risk_level", "Color": "blue"}, - {"Label": "min_risk_level", "Color": "green"}, - {"Label": "max_risk_level", "Color": "orange"}, - ], - data_collector_name="datacollector", - canvas_height=100, +from simulatingrisk.risky_food.server import ( + chart, + risk_chart, + total_agent_chart, + histogram, ) -total_agent_chart = mesa.visualization.ChartModule( - [ - {"Label": "num_agents", "Color": "gray"}, - ], - data_collector_name="datacollector", - canvas_height=100, -) server = mesa.visualization.ModularServer( RiskyFoodModel, # [chart, risk_chart, agent_risk_chart, total_agent_chart], - [chart, risk_chart, total_agent_chart], + [chart, risk_chart, total_agent_chart, histogram], "Risky Food", {"n": 20, "mode": "types"}, ) diff --git a/simulatingrisk/risky_food/server.py b/simulatingrisk/risky_food/server.py new file mode 100644 index 0000000..8e98a05 --- /dev/null +++ b/simulatingrisk/risky_food/server.py @@ -0,0 +1,68 @@ +import mesa +from mesa.visualization.ModularVisualization import VisualizationElement, CHART_JS_FILE +import numpy as np + + +chart = mesa.visualization.ChartModule( + [ + {"Label": "prob_notcontaminated", "Color": "blue"}, + {"Label": "contaminated", "Color": "red"}, + ], + data_collector_name="datacollector", + canvas_height=100, # default height is 200 +) +risk_chart = mesa.visualization.ChartModule( + [ + {"Label": "average_risk_level", "Color": "blue"}, + {"Label": "min_risk_level", "Color": "green"}, + {"Label": "max_risk_level", "Color": "orange"}, + ], + data_collector_name="datacollector", + canvas_height=100, +) + +total_agent_chart = mesa.visualization.ChartModule( + [ + {"Label": "num_agents", "Color": "gray"}, + ], + data_collector_name="datacollector", + canvas_height=100, +) + + +# histogram chart from mesa tutorial +class HistogramModule(VisualizationElement): + package_includes = [CHART_JS_FILE] + local_includes = ["HistogramModule.js"] + + def __init__(self, bins, canvas_height, canvas_width, label): + self.canvas_height = canvas_height + self.canvas_width = canvas_width + self.bins = bins + new_element = "new HistogramModule({}, {}, {}, {})" + new_element = new_element.format( + bins, canvas_width, canvas_height, '"%s"' % label + ) + self.js_code = "elements.push(" + new_element + ");" + + def render(self, model): + risk_levels = [agent.risk_level for agent in model.schedule.agents] + # generate a histogram of risk levels based on the specified bins + hist = np.histogram(risk_levels, bins=self.bins)[0] + return [int(x) for x in hist] + + +# bins for risk levels to chart as histogram +# match types used in the class, i.e. 0, 0.1, 0.2, 0.3, ... 1.0 +risk_bins = [r / 10 for r in range(12)] +histogram = HistogramModule(risk_bins, 200, 500, "risk levels") + +# server = mesa.visualization.ModularServer( +# RiskyFoodModel, +# # [chart, risk_chart, agent_risk_chart, total_agent_chart], +# [chart, risk_chart, total_agent_chart, histogram], +# "Risky Food", +# {"n": 20, "mode": "types"}, +# ) +# server.port = 8521 # The default +# server.launch() From 96227604f5a7f0c3812b3ae94d15a71c6b388067 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 25 Jul 2023 14:22:06 -0400 Subject: [PATCH 045/141] Avoid unit tests error calculating median on empty list --- simulatingrisk/risky_bet/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simulatingrisk/risky_bet/model.py b/simulatingrisk/risky_bet/model.py index 710f8f9..95fe43f 100644 --- a/simulatingrisk/risky_bet/model.py +++ b/simulatingrisk/risky_bet/model.py @@ -199,7 +199,7 @@ def max_agent_wealth(self): @property def risk_median(self): # calculate median of current agent risk levels - if not self.agent_risk_levels: + if not any(self.agent_risk_levels): # occasionally this complains about an empty list # hopefully only possible in unit tests... return From b4d3e23bf7b744db2716820223fa226764af9b1d Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 25 Jul 2023 14:37:37 -0400 Subject: [PATCH 046/141] Adapt risk histogram for risky bet server --- simulatingrisk/risky_bet/HistogramModule.js | 1 + simulatingrisk/risky_bet/run.py | 17 ++++++++++++++++- simulatingrisk/risky_food/server.py | 16 +++++----------- 3 files changed, 22 insertions(+), 12 deletions(-) create mode 120000 simulatingrisk/risky_bet/HistogramModule.js diff --git a/simulatingrisk/risky_bet/HistogramModule.js b/simulatingrisk/risky_bet/HistogramModule.js new file mode 120000 index 0000000..2c90cfa --- /dev/null +++ b/simulatingrisk/risky_bet/HistogramModule.js @@ -0,0 +1 @@ +../risky_food/HistogramModule.js \ No newline at end of file diff --git a/simulatingrisk/risky_bet/run.py b/simulatingrisk/risky_bet/run.py index c92e2a7..8b7428f 100644 --- a/simulatingrisk/risky_bet/run.py +++ b/simulatingrisk/risky_bet/run.py @@ -2,6 +2,8 @@ from simulatingrisk.risky_bet.model import RiskyBetModel, divergent_colors from simulatingrisk.risky_bet.server import agent_portrayal +from simulatingrisk.risky_food.server import RiskHistogramModule + grid_size = 20 @@ -35,6 +37,7 @@ {"Label": "risk_max", "Color": divergent_colors[-1]}, ], data_collector_name="datacollector", + canvas_height=100, ) world_chart = mesa.visualization.ChartModule( [ @@ -42,10 +45,22 @@ {"Label": "risky_bet", "Color": "blue"}, ], data_collector_name="datacollector", + canvas_height=100, ) + + +# generate bins for histogram, capturing 0-0.5 and 0.95-1.0 +risk_bins = [] +r = 0.05 +while r < 1.05: + risk_bins.append(round(r, 2)) + r += 0.1 +histogram = RiskHistogramModule(risk_bins, 175, 500, "risk levels") + + server = mesa.visualization.ModularServer( RiskyBetModel, - [grid, risk_chart, world_chart], + [grid, histogram, world_chart, risk_chart], "Risky Bet Simulation", model_params=model_params, ) diff --git a/simulatingrisk/risky_food/server.py b/simulatingrisk/risky_food/server.py index 8e98a05..f966b06 100644 --- a/simulatingrisk/risky_food/server.py +++ b/simulatingrisk/risky_food/server.py @@ -31,7 +31,8 @@ # histogram chart from mesa tutorial -class HistogramModule(VisualizationElement): +# https://mesa.readthedocs.io/en/stable/tutorials/adv_tutorial_legacy.html +class RiskHistogramModule(VisualizationElement): package_includes = [CHART_JS_FILE] local_includes = ["HistogramModule.js"] @@ -54,15 +55,8 @@ def render(self, model): # bins for risk levels to chart as histogram # match types used in the class, i.e. 0, 0.1, 0.2, 0.3, ... 1.0 +# NOTE: if we don't include 1.1, np.histogram groups 0.9 with 1.0 risk_bins = [r / 10 for r in range(12)] -histogram = HistogramModule(risk_bins, 200, 500, "risk levels") +histogram = RiskHistogramModule(risk_bins, 200, 500, "risk levels") -# server = mesa.visualization.ModularServer( -# RiskyFoodModel, -# # [chart, risk_chart, agent_risk_chart, total_agent_chart], -# [chart, risk_chart, total_agent_chart, histogram], -# "Risky Food", -# {"n": 20, "mode": "types"}, -# ) -# server.port = 8521 # The default -# server.launch() +# server is initialized in run.py From 1957a4b558f0d7588b55386ed7e0a6fb67727f27 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 25 Jul 2023 14:45:18 -0400 Subject: [PATCH 047/141] Move risk histogram code into common location for reuse --- .../histogram.js} | 0 simulatingrisk/charts/histogram.py | 29 +++++++++++++++++++ simulatingrisk/risky_bet/HistogramModule.js | 1 - simulatingrisk/risky_bet/run.py | 2 +- simulatingrisk/risky_food/server.py | 26 +---------------- 5 files changed, 31 insertions(+), 27 deletions(-) rename simulatingrisk/{risky_food/HistogramModule.js => charts/histogram.js} (100%) create mode 100644 simulatingrisk/charts/histogram.py delete mode 120000 simulatingrisk/risky_bet/HistogramModule.js diff --git a/simulatingrisk/risky_food/HistogramModule.js b/simulatingrisk/charts/histogram.js similarity index 100% rename from simulatingrisk/risky_food/HistogramModule.js rename to simulatingrisk/charts/histogram.js diff --git a/simulatingrisk/charts/histogram.py b/simulatingrisk/charts/histogram.py new file mode 100644 index 0000000..692ae2a --- /dev/null +++ b/simulatingrisk/charts/histogram.py @@ -0,0 +1,29 @@ +import os +import numpy as np + +from mesa.visualization.ModularVisualization import VisualizationElement, CHART_JS_FILE + + +# histogram chart from mesa tutorial +# https://mesa.readthedocs.io/en/stable/tutorials/adv_tutorial_legacy.html +class RiskHistogramModule(VisualizationElement): + package_includes = [CHART_JS_FILE] + local_includes = ["histogram.js"] + # javascript is located in the same file as this python file + local_dir = os.path.dirname(os.path.realpath(__file__)) + + def __init__(self, bins, canvas_height, canvas_width, label): + self.canvas_height = canvas_height + self.canvas_width = canvas_width + self.bins = bins + new_element = "new HistogramModule({}, {}, {}, {})" + new_element = new_element.format( + bins, canvas_width, canvas_height, '"%s"' % label + ) + self.js_code = "elements.push(" + new_element + ");" + + def render(self, model): + risk_levels = [agent.risk_level for agent in model.schedule.agents] + # generate a histogram of risk levels based on the specified bins + hist = np.histogram(risk_levels, bins=self.bins)[0] + return [int(x) for x in hist] diff --git a/simulatingrisk/risky_bet/HistogramModule.js b/simulatingrisk/risky_bet/HistogramModule.js deleted file mode 120000 index 2c90cfa..0000000 --- a/simulatingrisk/risky_bet/HistogramModule.js +++ /dev/null @@ -1 +0,0 @@ -../risky_food/HistogramModule.js \ No newline at end of file diff --git a/simulatingrisk/risky_bet/run.py b/simulatingrisk/risky_bet/run.py index 8b7428f..736d2b4 100644 --- a/simulatingrisk/risky_bet/run.py +++ b/simulatingrisk/risky_bet/run.py @@ -2,7 +2,7 @@ from simulatingrisk.risky_bet.model import RiskyBetModel, divergent_colors from simulatingrisk.risky_bet.server import agent_portrayal -from simulatingrisk.risky_food.server import RiskHistogramModule +from simulatingrisk.charts.histogram import RiskHistogramModule grid_size = 20 diff --git a/simulatingrisk/risky_food/server.py b/simulatingrisk/risky_food/server.py index f966b06..ca209de 100644 --- a/simulatingrisk/risky_food/server.py +++ b/simulatingrisk/risky_food/server.py @@ -1,7 +1,6 @@ import mesa -from mesa.visualization.ModularVisualization import VisualizationElement, CHART_JS_FILE -import numpy as np +from simulatingrisk.charts.histogram import RiskHistogramModule chart = mesa.visualization.ChartModule( [ @@ -30,29 +29,6 @@ ) -# histogram chart from mesa tutorial -# https://mesa.readthedocs.io/en/stable/tutorials/adv_tutorial_legacy.html -class RiskHistogramModule(VisualizationElement): - package_includes = [CHART_JS_FILE] - local_includes = ["HistogramModule.js"] - - def __init__(self, bins, canvas_height, canvas_width, label): - self.canvas_height = canvas_height - self.canvas_width = canvas_width - self.bins = bins - new_element = "new HistogramModule({}, {}, {}, {})" - new_element = new_element.format( - bins, canvas_width, canvas_height, '"%s"' % label - ) - self.js_code = "elements.push(" + new_element + ");" - - def render(self, model): - risk_levels = [agent.risk_level for agent in model.schedule.agents] - # generate a histogram of risk levels based on the specified bins - hist = np.histogram(risk_levels, bins=self.bins)[0] - return [int(x) for x in hist] - - # bins for risk levels to chart as histogram # match types used in the class, i.e. 0, 0.1, 0.2, 0.3, ... 1.0 # NOTE: if we don't include 1.1, np.histogram groups 0.9 with 1.0 From def4129699bd5c68ab878bd186f57604d4db5d44 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 25 Jul 2023 15:20:25 -0400 Subject: [PATCH 048/141] Update batch run script for revised version of risky food sim --- simulatingrisk/batch_run.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/simulatingrisk/batch_run.py b/simulatingrisk/batch_run.py index 4d794c1..58ba137 100755 --- a/simulatingrisk/batch_run.py +++ b/simulatingrisk/batch_run.py @@ -31,11 +31,9 @@ def riskyfood_batch_run(): results = batch_run( RiskyFoodModel, # only parameter to this one currently is number of agents - parameters={ - "n": 10, # [10, 20, 30], # 100], - }, - iterations=5, - max_steps=22, # population gets too large after 25/26 rounds... + parameters={"n": 110, "mode": "types"}, + iterations=10, # this one is faster, let's run more iterations + max_steps=100, number_processes=1, # set None to use all available; set 1 for jupyter data_collection_period=1, display_progress=True, From e50f395092e008a12555e6302265903eff310cf1 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 25 Jul 2023 15:20:42 -0400 Subject: [PATCH 049/141] Run analysis against batch run of new risky food sim --- notebooks/riskyfood_batch_analysis.ipynb | 562 ++++++++++++----------- 1 file changed, 300 insertions(+), 262 deletions(-) diff --git a/notebooks/riskyfood_batch_analysis.ipynb b/notebooks/riskyfood_batch_analysis.ipynb index 7a84e67..5d65495 100644 --- a/notebooks/riskyfood_batch_analysis.ipynb +++ b/notebooks/riskyfood_batch_analysis.ipynb @@ -5,11 +5,21 @@ "execution_count": 1, "id": "e7850a1d-09ba-42d9-a59d-7b93533b1274", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/var/folders/tn/1_gbhpks7hqbkbln2gjhcdvm0000gp/T/ipykernel_19041/3038069395.py:4: DtypeWarning: Columns (11) have mixed types. Specify dtype option on import or set low_memory=False.\n", + " df = pd.read_csv(\"../riskyfood_2023-07-25T150110_995385.csv\")\n" + ] + } + ], "source": [ "import pandas as pd\n", "\n", - "df = pd.read_csv(\"../riskyfood_2023-07-19T113234_405932.csv\")" + "#df = pd.read_csv(\"../riskyfood_2023-07-19T113234_405932.csv\")\n", + "df = pd.read_csv(\"../riskyfood_2023-07-25T150110_995385.csv\")" ] }, { @@ -43,6 +53,7 @@ " iteration\n", " Step\n", " n\n", + " mode\n", " prob_notcontaminated\n", " contaminated\n", " average_risk_level\n", @@ -60,13 +71,14 @@ " 0\n", " 0\n", " 0\n", - " 10\n", - " 0.909222\n", - " 1\n", - " 0.547708\n", - " 0.01239\n", - " 0.961562\n", - " 10\n", + " 110\n", + " types\n", + " 0.765003\n", + " 0\n", + " 0.500000\n", + " 0.0\n", + " 1.0\n", + " 110\n", " NaN\n", " NaN\n", " NaN\n", @@ -76,15 +88,16 @@ " 0\n", " 0\n", " 1\n", - " 10\n", - " 0.981479\n", - " 0\n", - " 0.525926\n", - " 0.01239\n", - " 0.961562\n", - " 19\n", + " 110\n", + " types\n", + " 0.284317\n", + " 1\n", + " 0.585714\n", + " 0.0\n", + " 1.0\n", + " 140\n", + " 0-0\n", " 0.0\n", - " 0.818438\n", " 2.0\n", " \n", " \n", @@ -92,31 +105,33 @@ " 0\n", " 0\n", " 1\n", - " 10\n", - " 0.981479\n", - " 0\n", - " 0.525926\n", - " 0.01239\n", - " 0.961562\n", - " 19\n", - " 1.0\n", - " 0.961562\n", + " 110\n", + " types\n", + " 0.284317\n", + " 1\n", + " 0.585714\n", + " 0.0\n", " 1.0\n", + " 140\n", + " 0-1\n", + " 0.0\n", + " 2.0\n", " \n", " \n", " 3\n", " 0\n", " 0\n", " 1\n", - " 10\n", - " 0.981479\n", - " 0\n", - " 0.525926\n", - " 0.01239\n", - " 0.961562\n", - " 19\n", - " 2.0\n", - " 0.095935\n", + " 110\n", + " types\n", + " 0.284317\n", + " 1\n", + " 0.585714\n", + " 0.0\n", + " 1.0\n", + " 140\n", + " 0-2\n", + " 0.0\n", " 2.0\n", " \n", " \n", @@ -124,15 +139,16 @@ " 0\n", " 0\n", " 1\n", - " 10\n", - " 0.981479\n", - " 0\n", - " 0.525926\n", - " 0.01239\n", - " 0.961562\n", - " 19\n", - " 3.0\n", - " 0.012390\n", + " 110\n", + " types\n", + " 0.284317\n", + " 1\n", + " 0.585714\n", + " 0.0\n", + " 1.0\n", + " 140\n", + " 0-3\n", + " 0.0\n", " 2.0\n", " \n", " \n", @@ -140,26 +156,26 @@ "" ], "text/plain": [ - " RunId iteration Step n prob_notcontaminated contaminated \n", - "0 0 0 0 10 0.909222 1 \\\n", - "1 0 0 1 10 0.981479 0 \n", - "2 0 0 1 10 0.981479 0 \n", - "3 0 0 1 10 0.981479 0 \n", - "4 0 0 1 10 0.981479 0 \n", + " RunId iteration Step n mode prob_notcontaminated contaminated \n", + "0 0 0 0 110 types 0.765003 0 \\\n", + "1 0 0 1 110 types 0.284317 1 \n", + "2 0 0 1 110 types 0.284317 1 \n", + "3 0 0 1 110 types 0.284317 1 \n", + "4 0 0 1 110 types 0.284317 1 \n", "\n", - " average_risk_level min_risk_level max_risk_level num_agents AgentID \n", - "0 0.547708 0.01239 0.961562 10 NaN \\\n", - "1 0.525926 0.01239 0.961562 19 0.0 \n", - "2 0.525926 0.01239 0.961562 19 1.0 \n", - "3 0.525926 0.01239 0.961562 19 2.0 \n", - "4 0.525926 0.01239 0.961562 19 3.0 \n", + " average_risk_level min_risk_level max_risk_level num_agents AgentID \n", + "0 0.500000 0.0 1.0 110 NaN \\\n", + "1 0.585714 0.0 1.0 140 0-0 \n", + "2 0.585714 0.0 1.0 140 0-1 \n", + "3 0.585714 0.0 1.0 140 0-2 \n", + "4 0.585714 0.0 1.0 140 0-3 \n", "\n", " risk_level payoff \n", "0 NaN NaN \n", - "1 0.818438 2.0 \n", - "2 0.961562 1.0 \n", - "3 0.095935 2.0 \n", - "4 0.012390 2.0 " + "1 0.0 2.0 \n", + "2 0.0 2.0 \n", + "3 0.0 2.0 \n", + "4 0.0 2.0 " ] }, "execution_count": 2, @@ -181,30 +197,18 @@ "data": { "text/plain": [ "Step\n", - "22 18267295\n", - "21 8169007\n", - "20 5594441\n", - "19 2640823\n", - "18 1716515\n", - "17 796653\n", - "16 441027\n", - "15 291215\n", - "14 219864\n", - "13 116250\n", - "12 51708\n", - "11 23970\n", - "10 14406\n", - "9 6748\n", - "8 3604\n", - "7 1687\n", - "6 1135\n", - "5 563\n", - "4 299\n", - "3 153\n", - "2 84\n", - "1 50\n", - "0 5\n", - "Name: count, dtype: int64" + "94 83302\n", + "29 43986\n", + "95 43622\n", + "93 41702\n", + "98 25702\n", + " ... \n", + "54 348\n", + "57 347\n", + "59 315\n", + "58 285\n", + "0 10\n", + "Name: count, Length: 101, dtype: int64" ] }, "execution_count": 3, @@ -213,7 +217,7 @@ } ], "source": [ - "# we couldn't do 100 steps for these because of the population explosion problem\n", + "# on the first version of the risky food sim, we couldn't do 100 steps for these because of the population explosion problem\n", "# how many steps did we end up with?\n", "df.Step.value_counts()\n" ] @@ -221,6 +225,28 @@ { "cell_type": "code", "execution_count": 4, + "id": "500f0a88-b880-43c0-9e8d-67a37971fb39", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "100" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "last_step_n = max(df.Step)\n", + "last_step_n" + ] + }, + { + "cell_type": "code", + "execution_count": 5, "id": "1b79f2fb-310e-4007-8e90-633848c3e3d6", "metadata": {}, "outputs": [ @@ -249,6 +275,7 @@ " iteration\n", " Step\n", " n\n", + " mode\n", " prob_notcontaminated\n", " contaminated\n", " average_risk_level\n", @@ -262,84 +289,89 @@ " \n", " \n", " \n", - " 9857152\n", + " 2090\n", " 0\n", " 0\n", - " 22\n", - " 10\n", - " 0.305690\n", + " 100\n", + " 110\n", + " types\n", + " 0.452254\n", " 0\n", - " 0.463917\n", - " 0.012390\n", - " 0.961562\n", - " 12734950\n", + " 0.009091\n", " 0.0\n", - " 0.818438\n", - " 1.0\n", + " 0.1\n", + " 11\n", + " 0-0\n", + " 0.0\n", + " 2.0\n", " \n", " \n", - " 9857153\n", + " 2091\n", " 0\n", " 0\n", - " 22\n", - " 10\n", - " 0.305690\n", + " 100\n", + " 110\n", + " types\n", + " 0.452254\n", " 0\n", - " 0.463917\n", - " 0.012390\n", - " 0.961562\n", - " 12734950\n", - " 1.0\n", - " 0.961562\n", - " 1.0\n", + " 0.009091\n", + " 0.0\n", + " 0.1\n", + " 11\n", + " 0-1\n", + " 0.0\n", + " 2.0\n", " \n", " \n", - " 9857154\n", + " 2092\n", " 0\n", " 0\n", - " 22\n", - " 10\n", - " 0.305690\n", + " 100\n", + " 110\n", + " types\n", + " 0.452254\n", " 0\n", - " 0.463917\n", - " 0.012390\n", - " 0.961562\n", - " 12734950\n", - " 2.0\n", - " 0.095935\n", + " 0.009091\n", + " 0.0\n", + " 0.1\n", + " 11\n", + " 0-2\n", + " 0.0\n", " 2.0\n", " \n", " \n", - " 9857155\n", + " 2093\n", " 0\n", " 0\n", - " 22\n", - " 10\n", - " 0.305690\n", + " 100\n", + " 110\n", + " types\n", + " 0.452254\n", " 0\n", - " 0.463917\n", - " 0.012390\n", - " 0.961562\n", - " 12734950\n", - " 3.0\n", - " 0.012390\n", + " 0.009091\n", + " 0.0\n", + " 0.1\n", + " 11\n", + " 0-3\n", + " 0.0\n", " 2.0\n", " \n", " \n", - " 9857156\n", + " 2094\n", " 0\n", " 0\n", - " 22\n", - " 10\n", - " 0.305690\n", + " 100\n", + " 110\n", + " types\n", + " 0.452254\n", " 0\n", - " 0.463917\n", - " 0.012390\n", - " 0.961562\n", - " 12734950\n", - " 4.0\n", - " 0.735042\n", - " 1.0\n", + " 0.009091\n", + " 0.0\n", + " 0.1\n", + " 11\n", + " 0-4\n", + " 0.0\n", + " 2.0\n", " \n", " \n", " ...\n", @@ -356,143 +388,149 @@ " ...\n", " ...\n", " ...\n", + " ...\n", " \n", " \n", - " 38357497\n", - " 4\n", - " 4\n", - " 22\n", - " 10\n", - " 0.614945\n", - " 0\n", - " 0.347472\n", - " 0.240855\n", - " 0.860197\n", - " 3282480\n", - " 5402634.0\n", - " 0.240855\n", - " 2.0\n", + " 635962\n", + " 9\n", + " 9\n", + " 100\n", + " 110\n", + " types\n", + " 0.003153\n", + " 1\n", + " 0.996109\n", + " 0.0\n", + " 1.0\n", + " 2570\n", + " 18598\n", + " 1.0\n", + " 1.0\n", " \n", " \n", - " 38357498\n", - " 4\n", - " 4\n", - " 22\n", - " 10\n", - " 0.614945\n", - " 0\n", - " 0.347472\n", - " 0.240855\n", - " 0.860197\n", - " 3282480\n", - " 5402636.0\n", - " 0.240855\n", - " 2.0\n", + " 635963\n", + " 9\n", + " 9\n", + " 100\n", + " 110\n", + " types\n", + " 0.003153\n", + " 1\n", + " 0.996109\n", + " 0.0\n", + " 1.0\n", + " 2570\n", + " 18599\n", + " 1.0\n", + " 1.0\n", " \n", " \n", - " 38357499\n", - " 4\n", - " 4\n", - " 22\n", - " 10\n", - " 0.614945\n", - " 0\n", - " 0.347472\n", - " 0.240855\n", - " 0.860197\n", - " 3282480\n", - " 5402638.0\n", - " 0.240855\n", - " 2.0\n", + " 635964\n", + " 9\n", + " 9\n", + " 100\n", + " 110\n", + " types\n", + " 0.003153\n", + " 1\n", + " 0.996109\n", + " 0.0\n", + " 1.0\n", + " 2570\n", + " 18600\n", + " 1.0\n", + " 1.0\n", " \n", " \n", - " 38357500\n", - " 4\n", - " 4\n", - " 22\n", - " 10\n", - " 0.614945\n", - " 0\n", - " 0.347472\n", - " 0.240855\n", - " 0.860197\n", - " 3282480\n", - " 5402640.0\n", - " 0.240855\n", - " 2.0\n", + " 635965\n", + " 9\n", + " 9\n", + " 100\n", + " 110\n", + " types\n", + " 0.003153\n", + " 1\n", + " 0.996109\n", + " 0.0\n", + " 1.0\n", + " 2570\n", + " 18601\n", + " 1.0\n", + " 1.0\n", " \n", " \n", - " 38357501\n", - " 4\n", - " 4\n", - " 22\n", - " 10\n", - " 0.614945\n", - " 0\n", - " 0.347472\n", - " 0.240855\n", - " 0.860197\n", - " 3282480\n", - " 5402642.0\n", - " 0.240855\n", - " 2.0\n", + " 635966\n", + " 9\n", + " 9\n", + " 100\n", + " 110\n", + " types\n", + " 0.003153\n", + " 1\n", + " 0.996109\n", + " 0.0\n", + " 1.0\n", + " 2570\n", + " 18602\n", + " 1.0\n", + " 1.0\n", " \n", " \n", "\n", - "

18267295 rows × 13 columns

\n", + "

10342 rows × 14 columns

\n", "" ], "text/plain": [ - " RunId iteration Step n prob_notcontaminated contaminated \n", - "9857152 0 0 22 10 0.305690 0 \\\n", - "9857153 0 0 22 10 0.305690 0 \n", - "9857154 0 0 22 10 0.305690 0 \n", - "9857155 0 0 22 10 0.305690 0 \n", - "9857156 0 0 22 10 0.305690 0 \n", - "... ... ... ... .. ... ... \n", - "38357497 4 4 22 10 0.614945 0 \n", - "38357498 4 4 22 10 0.614945 0 \n", - "38357499 4 4 22 10 0.614945 0 \n", - "38357500 4 4 22 10 0.614945 0 \n", - "38357501 4 4 22 10 0.614945 0 \n", + " RunId iteration Step n mode prob_notcontaminated \n", + "2090 0 0 100 110 types 0.452254 \\\n", + "2091 0 0 100 110 types 0.452254 \n", + "2092 0 0 100 110 types 0.452254 \n", + "2093 0 0 100 110 types 0.452254 \n", + "2094 0 0 100 110 types 0.452254 \n", + "... ... ... ... ... ... ... \n", + "635962 9 9 100 110 types 0.003153 \n", + "635963 9 9 100 110 types 0.003153 \n", + "635964 9 9 100 110 types 0.003153 \n", + "635965 9 9 100 110 types 0.003153 \n", + "635966 9 9 100 110 types 0.003153 \n", "\n", - " average_risk_level min_risk_level max_risk_level num_agents \n", - "9857152 0.463917 0.012390 0.961562 12734950 \\\n", - "9857153 0.463917 0.012390 0.961562 12734950 \n", - "9857154 0.463917 0.012390 0.961562 12734950 \n", - "9857155 0.463917 0.012390 0.961562 12734950 \n", - "9857156 0.463917 0.012390 0.961562 12734950 \n", - "... ... ... ... ... \n", - "38357497 0.347472 0.240855 0.860197 3282480 \n", - "38357498 0.347472 0.240855 0.860197 3282480 \n", - "38357499 0.347472 0.240855 0.860197 3282480 \n", - "38357500 0.347472 0.240855 0.860197 3282480 \n", - "38357501 0.347472 0.240855 0.860197 3282480 \n", + " contaminated average_risk_level min_risk_level max_risk_level \n", + "2090 0 0.009091 0.0 0.1 \\\n", + "2091 0 0.009091 0.0 0.1 \n", + "2092 0 0.009091 0.0 0.1 \n", + "2093 0 0.009091 0.0 0.1 \n", + "2094 0 0.009091 0.0 0.1 \n", + "... ... ... ... ... \n", + "635962 1 0.996109 0.0 1.0 \n", + "635963 1 0.996109 0.0 1.0 \n", + "635964 1 0.996109 0.0 1.0 \n", + "635965 1 0.996109 0.0 1.0 \n", + "635966 1 0.996109 0.0 1.0 \n", "\n", - " AgentID risk_level payoff \n", - "9857152 0.0 0.818438 1.0 \n", - "9857153 1.0 0.961562 1.0 \n", - "9857154 2.0 0.095935 2.0 \n", - "9857155 3.0 0.012390 2.0 \n", - "9857156 4.0 0.735042 1.0 \n", - "... ... ... ... \n", - "38357497 5402634.0 0.240855 2.0 \n", - "38357498 5402636.0 0.240855 2.0 \n", - "38357499 5402638.0 0.240855 2.0 \n", - "38357500 5402640.0 0.240855 2.0 \n", - "38357501 5402642.0 0.240855 2.0 \n", + " num_agents AgentID risk_level payoff \n", + "2090 11 0-0 0.0 2.0 \n", + "2091 11 0-1 0.0 2.0 \n", + "2092 11 0-2 0.0 2.0 \n", + "2093 11 0-3 0.0 2.0 \n", + "2094 11 0-4 0.0 2.0 \n", + "... ... ... ... ... \n", + "635962 2570 18598 1.0 1.0 \n", + "635963 2570 18599 1.0 1.0 \n", + "635964 2570 18600 1.0 1.0 \n", + "635965 2570 18601 1.0 1.0 \n", + "635966 2570 18602 1.0 1.0 \n", "\n", - "[18267295 rows x 13 columns]" + "[10342 rows x 14 columns]" ] }, - "execution_count": 4, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# get data from the last step of each run\n", - "last_step = df[df.Step == 22]\n", + "last_step = df[df.Step == last_step_n]\n", "last_step" ] }, @@ -506,7 +544,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "927567b5-bb71-41ba-9078-65631b9f82a3", "metadata": {}, "outputs": [ @@ -516,13 +554,13 @@ "" ] }, - "execution_count": 5, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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jcq+/OUvV3nl9evOO+/Mzj333OzWW2/NIiJbtGjRdvdfs2ZNNmzYsGz27NnZY489ln3/+9/PGhoasrvvvjvX/dZNaBx11FHZrFmzej/u6urKRo8enV100UVb3f+DH/xgNm3atM22HX300dlnPvOZpOscSPLO/H9t2rQpa2pqyn7605+mWuKAU8nMN23alL397W/PfvzjH2czZ84UGhXIO/crr7wyO/DAA7NXX321WksccPLOfNasWdk73/nOzbbNnj07O+aYY5Kuc6DakdD42te+lh166KGbbTv99NOzqVOn5rqvuvjRSc/b00+ePLl3W19vT//AAw9stn9ExNSpU7e5P5urZOb/q7OzM8rlcowYMSLVMgeUSmf+7W9/O0aOHBmf+MQnqrHMAaeSuf/iF7+ISZMmxaxZs2LvvfeO8ePHx3e+853o6uqq1rLrWiUzf/vb3x4rVqzo/fHKmjVr4s4774yTTz65KmveFfXX99Hk797aHyp5e/q1a9dudf+1a9cmW+dAUsnM/9fXv/71GD169BYnKltXyczvu+++uPbaa2PlypVVWOHAVMnc16xZE7/+9a/jwx/+cNx5552xevXq+PznPx/lcjnmzJlTjWXXtUpmPmPGjFi3bl0ce+yxkWVZbNq0KT772c/GN77xjWoseZe0re+jHR0d8a9//St22223HTpOXVzRoP7MmzcvFi5cGIsWLYqhQ4fWejkD0vr16+OMM86Ia665JlpaWmq9nF1Kd3d3jBw5Mn70ox/FkUceGaeffnqce+65cdVVV9V6aQPWsmXL4jvf+U788Ic/jN///vdx6623xh133BHnn39+rZdGH+riikYlb0+/zz775NqfzVUy8x6XXHJJzJs3L371q1/FhAkTUi5zQMk78yeffDKefvrpmD59eu+27u7uiIgYPHhwPPHEEzF27Ni0ix4AKjnXR40aFY2NjdHQ0NC77Y1vfGOsXbs2Xn311RgyZEjSNde7SmZ+3nnnxRlnnBGf/OQnIyLisMMOi40bN8anP/3pOPfcc2PQIP9v7m/b+j7a3Ny8w1czIurkikYlb08/adKkzfaPiFi6dKm3s99Blcw8IuLiiy+O888/P+6+++6YOHFiNZY6YOSd+SGHHBKrVq2KlStX9v455ZRT4sQTT4yVK1dGa2trNZdftyo514855phYvXp1b9hFRPz5z3+OUaNGiYwdUMnMOzs7t4iJntDLvGVXEv32fTTf81RrZ+HChVmxWMzmz5+fPfbYY9mnP/3pbPjw4dnatWuzLMuyM844Izv77LN797///vuzwYMHZ5dcckn2+OOPZ3PmzPHy1pzyznzevHnZkCFDsltuuSV7/vnne/+sX7++Vp9C3ck78//lVSeVyTv3Z555JmtqasrOOuus7Iknnshuv/32bOTIkdkFF1xQq0+h7uSd+Zw5c7KmpqbsxhtvzNasWZMtWbIkGzt2bPbBD36wVp9C3Vm/fn32yCOPZI888kgWEdlll12WPfLII9lf//rXLMuy7Oyzz87OOOOM3v17Xt761a9+NXv88cezK664YmC/vDXLsuz73/9+NmbMmGzIkCHZUUcdlT344IO9f3f88cdnM2fO3Gz/n/3sZ9m4ceOyIUOGZIceemh2xx13VHnF9S/PzPfbb78sIrb4M2fOnOovvI7lPc//m9CoXN65//a3v82OPvrorFgsZgceeGB24YUXZps2baryqutbnpmXy+Wsra0tGzt2bDZ06NCstbU1+/znP5/985//rP7C69RvfvObrT5G98x55syZ2fHHH7/FbY444ohsyJAh2YEHHphdd911ue/X28QDAMnUxXM0AID6JDQAgGSEBgCQjNAAAJIRGgBAMkIDAEhGaAAAyQgNACAZoQEAJCM0AIBkhAYAkIzQAACS+X+0MMTayYhK7AAAAABJRU5ErkJggg==", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjkAAAGdCAYAAADwjmIIAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAAApKklEQVR4nO3df3RU9Z3/8VcSMpOEkoTAya8aMeoqv0uFGiNi/RESBV2wrJUlxWwboWriFnIOVBQDBBSJgPxKzaIiehYKuissBTZkGhYRCD9MyYqAqAst7rozrOXH8EOSIbnfPzy5X8cAknRm0vnwfJyTc7z3vu9n3vdtgNe5dyaJsCzLEgAAgGEiO7oBAACAYCDkAAAAIxFyAACAkQg5AADASIQcAABgJEIOAAAwEiEHAAAYiZADAACM1KmjG+hIzc3N+uKLL9SlSxdFRER0dDsAAOAKWJal06dPKz09XZGRl75fc1WHnC+++EIZGRkd3QYAAGiHzz//XNdcc80lj1/VIadLly6Svh5SfHx8wNb1+Xyqrq5Wbm6uoqOjA7Yu/DHn0GHWocGcQ4M5h0Yw5+z1epWRkWH/O34pV3XIaXlEFR8fH/CQExcXp/j4eP4ABRFzDh1mHRrMOTSYc2iEYs7f9VYT3ngMAACMRMgBAABGIuQAAAAjEXIAAICRCDkAAMBIhBwAAGAkQg4AADASIQcAABiJkAMAAIxEyAEAAEYi5AAAACMRcgAAgJEIOQAAwEiEHAAAYKROHd0AAAD4btc9vaGjW2gTZ5Sl8ls7tgfu5AAAACMRcgAAgJEIOQAAwEhtDjlbt27Vgw8+qPT0dEVERGjt2rV+xy3LUmlpqdLS0hQbG6ucnBx9+umnfjXHjx9Xfn6+4uPjlZiYqMLCQp05c8av5sMPP9SQIUMUExOjjIwMlZeXt+rlnXfeUc+ePRUTE6N+/fpp48aNbb0cAABgqDaHnLNnz+oHP/iBKioqLnq8vLxcixYtUmVlpXbt2qXOnTsrLy9P58+ft2vy8/O1f/9+uVwurV+/Xlu3btX48ePt416vV7m5uerRo4fq6ur00ksvafr06Vq6dKlds2PHDv393/+9CgsLtXfvXo0cOVIjR47URx991NZLAgAABmrzp6vuv/9+3X///Rc9ZlmWFixYoKlTp2rEiBGSpLfeekspKSlau3atRo8erYMHD6qqqkp79uzRoEGDJEmLFy/WsGHDNHfuXKWnp2vFihVqbGzUsmXL5HA41KdPH9XX12v+/Pl2GFq4cKHuu+8+TZo0SZI0c+ZMuVwuLVmyRJWVle0aBgAAMEdA35Nz5MgRud1u5eTk2PsSEhKUlZWl2tpaSVJtba0SExPtgCNJOTk5ioyM1K5du+yaO++8Uw6Hw67Jy8vToUOHdOLECbvmm6/TUtPyOgAA4OoW0J+T43a7JUkpKSl++1NSUuxjbrdbycnJ/k106qSkpCS/mszMzFZrtBzr2rWr3G73ZV/nYhoaGtTQ0GBve71eSZLP55PP57vi6/wuLWsFck20xpxDh1mHBnMOjXCdszPK6ugW2sQZ+XW/wZjzla55Vf0wwNmzZ2vGjBmt9ldXVysuLi7gr+dyuQK+JlpjzqHDrEODOYdGuM25o3+wXnsFY87nzp27orqAhpzU1FRJksfjUVpamr3f4/FowIABds2xY8f8zrtw4YKOHz9un5+amiqPx+NX07L9XTUtxy9mypQpKikpsbe9Xq8yMjKUm5ur+Pj4tlzqZfl8PrlcLg0dOlTR0dEBWxf+mHPoMOvQYM6hEa5z7jt9U0e30CbOSEszBzUHZc4tT2K+S0BDTmZmplJTU1VTU2OHGq/Xq127dumJJ56QJGVnZ+vkyZOqq6vTwIEDJUmbN29Wc3OzsrKy7Jpnn31WPp/PHozL5dLNN9+srl272jU1NTWaMGGC/foul0vZ2dmX7M/pdMrpdLbaHx0dHZRv9GCtC3/MOXSYdWgw59AItzk3NEV0dAvtEow5X+l6bX7j8ZkzZ1RfX6/6+npJX7/ZuL6+XkePHlVERIQmTJigWbNmad26ddq3b58effRRpaena+TIkZKkXr166b777tO4ceO0e/dubd++XcXFxRo9erTS09MlSWPGjJHD4VBhYaH279+v1atXa+HChX53YX71q1+pqqpK8+bN08cff6zp06frgw8+UHFxcVsvCQAAGKjNd3I++OAD3X333fZ2S/AoKCjQ8uXLNXnyZJ09e1bjx4/XyZMndccdd6iqqkoxMTH2OStWrFBxcbHuvfdeRUZGatSoUVq0aJF9PCEhQdXV1SoqKtLAgQPVvXt3lZaW+v0sndtvv10rV67U1KlT9cwzz+hv/uZvtHbtWvXt27ddgwAAAGZpc8i56667ZFmXfod3RESEysrKVFZWdsmapKQkrVy58rKv079/f73//vuXrXn44Yf18MMPX75hAABwVeJ3VwEAACMRcgAAgJEIOQAAwEiEHAAAYCRCDgAAMBIhBwAAGImQAwAAjETIAQAARiLkAAAAIxFyAACAkQg5AADASIQcAABgJEIOAAAwEiEHAAAYiZADAACMRMgBAABGIuQAAAAjEXIAAICRCDkAAMBIhBwAAGAkQg4AADASIQcAABiJkAMAAIxEyAEAAEYi5AAAACMRcgAAgJEIOQAAwEiEHAAAYCRCDgAAMBIhBwAAGImQAwAAjETIAQAARiLkAAAAIxFyAACAkQg5AADASIQcAABgJEIOAAAwEiEHAAAYiZADAACMRMgBAABGIuQAAAAjEXIAAICRCDkAAMBIhBwAAGAkQg4AADASIQcAABiJkAMAAIxEyAEAAEYi5AAAACMRcgAAgJEIOQAAwEiEHAAAYCRCDgAAMBIhBwAAGImQAwAAjETIAQAARiLkAAAAIxFyAACAkQIecpqamvTcc88pMzNTsbGxuuGGGzRz5kxZlmXXWJal0tJSpaWlKTY2Vjk5Ofr000/91jl+/Ljy8/MVHx+vxMREFRYW6syZM341H374oYYMGaKYmBhlZGSovLw80JcDAADCVMBDzpw5c/TKK69oyZIlOnjwoObMmaPy8nItXrzYrikvL9eiRYtUWVmpXbt2qXPnzsrLy9P58+ftmvz8fO3fv18ul0vr16/X1q1bNX78ePu41+tVbm6uevToobq6Or300kuaPn26li5dGuhLAgAAYahToBfcsWOHRowYoeHDh0uSrrvuOv32t7/V7t27JX19F2fBggWaOnWqRowYIUl66623lJKSorVr12r06NE6ePCgqqqqtGfPHg0aNEiStHjxYg0bNkxz585Venq6VqxYocbGRi1btkwOh0N9+vRRfX295s+f7xeGAADA1SngIef222/X0qVL9cknn+imm27Sf/7nf2rbtm2aP3++JOnIkSNyu93Kycmxz0lISFBWVpZqa2s1evRo1dbWKjEx0Q44kpSTk6PIyEjt2rVLDz30kGpra3XnnXfK4XDYNXl5eZozZ45OnDihrl27tuqtoaFBDQ0N9rbX65Uk+Xw++Xy+gM2gZa1AronWmHPoMOvQYM6hEa5zdkZZ3130V8QZ+XW/wZjzla4Z8JDz9NNPy+v1qmfPnoqKilJT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UaNGqaGhQXl5efrNb35jH4+KitL69ev1xBNPKDs7W507d1ZBQYHKysrsmszMTG3YsEETJ07UwoULdc011+i1115TXl5eR1wSAAD4KxOSkLNlyxa/7ZiYGFVUVKiiouKS5/To0UMbN2687Lp33XWX9u7dG4gWAQCAYfjdVQAAwEiEHAAAYCRCDgAAMBIhBwAAGImQAwAAjETIAQAARiLkAAAAIxFyAACAkQg5AADASIQcAABgJEIOAAAwEiEHAAAYiZADAACMRMgBAABGIuQAAAAjEXIAAICRCDkAAMBIhBwAAGAkQg4AADASIQcAABiJkAMAAIxEyAEAAEYi5AAAACMRcgAAgJEIOQAAwEiEHAAAYCRCDgAAMBIhBwAAGImQAwAAjETIAQAARiLkAAAAIxFyAACAkQg5AADASIQcAABgJEIOAAAwEiEHAAAYiZADAACMRMgBAABGIuQAAAAjEXIAAICRCDkAAMBIhBwAAGAkQg4AADASIQcAABiJkAMAAIxEyAEAAEYi5AAAACMRcgAAgJEIOQAAwEiEHAAAYCRCDgAAMBIhBwAAGImQAwAAjETIAQAARiLkAAAAIxFyAACAkQg5AADASAEPObNnz9aPfvQjdenSRcnJyRo5cqQOHTrkV3P+/HkVFRWpW7du+t73vqdRo0bJ4/H41Rw9elTDhw9XXFyckpOTNWnSJF24cMGvZsuWLbrlllvkdDp14403avny5YG+HAAAEKYCHnLee+89FRUVaefOnXK5XPL5fMrNzdXZs2ftmokTJ+p3v/ud3nnnHb333nv64osv9JOf/MQ+3tTUpOHDh6uxsVE7duzQm2++qeXLl6u0tNSuOXLkiIYPH667775b9fX1mjBhgh577DFt2rQp0JcEAADCUKdAL1hVVeW3vXz5ciUnJ6uurk533nmnTp06pddff10rV67UPffcI0l644031KtXL+3cuVO33XabqqurdeDAAf3+979XSkqKBgwYoJkzZ+rXv/61pk+fLofDocrKSmVmZmrevHmSpF69emnbtm16+eWXlZeXF+jLAgAAYSbo78k5deqUJCkpKUmSVFdXJ5/Pp5ycHLumZ8+euvbaa1VbWytJqq2tVb9+/ZSSkmLX5OXlyev1av/+/XbNN9doqWlZAwAAXN0Cfifnm5qbmzVhwgQNHjxYffv2lSS53W45HA4lJib6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", "text/plain": [ "
" ] @@ -538,7 +576,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "3adaf26c-4d0e-4fae-9cca-649b6bd6286e", "metadata": {}, "outputs": [ @@ -548,13 +586,13 @@ "" ] }, - "execution_count": 6, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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n3++nVJtcblcHMghRs97Bn0PPXoeevQ89DrruZ0Vkxa2LoiNjY3V5MmTVV5e3jrm9XpVXl6u9PT0DrdJT09vM1+SysrKfM4HAAC9m+23dXJycrRkyRJNmTJFU6dO1YYNG9TY2KilS5dKkhYvXqxBgwapsLBQknTzzTdrxowZWrdunTIzM7Vlyxbt3btXmzdvDuwjAQAAEcF2OLnhhhv0xRdf6K677lJtba0uu+wybd++vfWi15qaGkVF/XdBZvr06SouLtYdd9yhVatW6ZJLLlFJSYnS0tIC9yj85HQ6tXr16nZvISF46HnPoO+hR89Dj56HXrB67rDsfr4HAAAgiPhuHQAAYBTCCQAAMArhBAAAGIVwAgAAjBLx4WTjxo0aOnSo4uLiNG3aNL377rudzt+6datGjRqluLg4jRs3TqWlpSGqNHLY6fnjjz+uq666ShdccIEuuOACzZ49u8vnCB2ze6y32LJlixwOh7KysoJbYASy2/OTJ08qOztbycnJcjqdGjlyJOcYm+z2fMOGDbr00kvVt29fpaam6pZbbtFXX30VomrD365duzR//nylpKTI4XB0+r14LSoqKjRp0iQ5nU6NGDFCRUVF9u/YimBbtmyxYmNjraeeesp6//33reXLl1vnn3++VVdX1+H8t99+24qOjrbuu+8+64MPPrDuuOMOKyYmxtq/f3+IKw9fdnu+aNEia+PGjda+ffusDz/80Prxj39s9evXz/r3v/8d4srDm92+tzhy5Ig1aNAg66qrrrKuv/760BQbIez23O12W1OmTLGuvfZaa/fu3daRI0esiooKq7q6OsSVhy+7PX/++ectp9NpPf/889aRI0esHTt2WMnJydYtt9wS4srDV2lpqXX77bdbr7zyiiXJ2rZtW6fzDx8+bMXHx1s5OTnWBx98YD300ENWdHS0tX37dlv3G9HhZOrUqVZ2dnbr7ebmZislJcUqLCzscP4PfvADKzMzs83YtGnTrJ/97GdBrTOS2O35N509e9ZKSEiwnnnmmWCVGJH86fvZs2et6dOnW0888YS1ZMkSwolNdnv+6KOPWhdffLF15syZUJUYcez2PDs727r66qvbjOXk5FhXXnllUOuMVN0JJytXrrTGjh3bZuyGG26wMjIybN1XxL6tc+bMGVVVVWn27NmtY1FRUZo9e7YqKys73KaysrLNfEnKyMjwOR9t+dPzb2pqapLH41H//v2DVWbE8bfvd999txITE3XTTTeFosyI4k/P//SnPyk9PV3Z2dkaOHCg0tLSdO+996q5uTlUZYc1f3o+ffp0VVVVtb71c/jwYZWWluraa68NSc29UaB+jxr5rcSBcPz4cTU3N7f+5doWAwcO1EcffdThNrW1tR3Or62tDVqdkcSfnn/T7373O6WkpLQ7uOGbP33fvXu3nnzySVVXV4egwsjjT88PHz6sN998Uz/84Q9VWlqqQ4cO6Ze//KU8Ho9Wr14dirLDmj89X7RokY4fP65vf/vbsixLZ8+e1c9//nOtWrUqFCX3Sr5+jzY0NOj//u//1Ldv327tJ2JXThB+1qxZoy1btmjbtm2Ki4vr6XIi1qlTp3TjjTfq8ccf14ABA3q6nF7D6/UqMTFRmzdv1uTJk3XDDTfo9ttv16ZNm3q6tIhVUVGhe++9V4888ojee+89vfLKK3rttddUUFDQ06WhCxG7cjJgwABFR0errq6uzXhdXZ2SkpI63CYpKcnWfLTlT89brF27VmvWrNEbb7yh8ePHB7PMiGO37//85z/1ySefaP78+a1jXq9XktSnTx8dPHhQw4cPD27RYc6fYz05OVkxMTGKjo5uHRs9erRqa2t15swZxcbGBrXmcOdPz++8807deOONWrZsmSRp3Lhxamxs1E9/+lPdfvvtbb4HDoHh6/eoy+Xq9qqJFMErJ7GxsZo8ebLKy8tbx7xer8rLy5Went7hNunp6W3mS1JZWZnP+WjLn55L0n333aeCggJt375dU6ZMCUWpEcVu30eNGqX9+/erurq69ee6667TrFmzVF1drdTU1FCWH5b8OdavvPJKHTp0qDUIStI//vEPJScnE0y6wZ+eNzU1tQsgLeHQ4mvlgiJgv0ftXasbXrZs2WI5nU6rqKjI+uCDD6yf/vSn1vnnn2/V1tZalmVZN954o3Xbbbe1zn/77betPn36WGvXrrU+/PBDa/Xq1XyU2Ca7PV+zZo0VGxtrvfzyy9axY8daf06dOtVTDyEs2e37N/FpHfvs9rympsZKSEiwVqxYYR08eNB69dVXrcTEROv3v/99Tz2EsGO356tXr7YSEhKsF154wTp8+LC1c+dOa/jw4dYPfvCDnnoIYefUqVPWvn37rH379lmSrAceeMDat2+fdfToUcuyLOu2226zbrzxxtb5LR8lvvXWW60PP/zQ2rhxIx8l7shDDz1kDRkyxIqNjbWmTp1qvfPOO63/NmPGDGvJkiVt5r/00kvWyJEjrdjYWGvs2LHWa6+9FuKKw5+dnl900UWWpHY/q1evDn3hYc7usf6/CCf+sdvzPXv2WNOmTbOcTqd18cUXW/fcc4919uzZEFcd3uz03OPxWHl5edbw4cOtuLg4KzU11frlL39pffnll6EvPEz95S9/6fAc3dLnJUuWWDNmzGi3zWWXXWbFxsZaF198sfX000/bvl+HZbG2BQAAzBGx15wAAIDwRDgBAABGIZwAAACjEE4AAIBRCCcAAMAohBMAAGAUwgkAADAK4QQAABiFcAIAAIxCOAEAAEYhnAAAAKMQTgAAgFH+H5t33StUTmoFAAAAAElFTkSuQmCC", + "image/png": "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", "text/plain": [ "
" ] @@ -570,17 +608,17 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "c26ce680-6892-42cc-80ea-20cf2361c9af", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "array([0, 1, 2, 3, 4])" + "array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])" ] }, - "execution_count": 7, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -593,15 +631,15 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 14, "id": "b8a09aaa-3e24-4881-a59a-941b00be1319", "metadata": {}, "outputs": [ { "data": { - "image/png": "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+9nP6nsmAAAAAADsFuoKtu+4446cffbZ+d73vpcVK1akq6srJ510UrZs2dLvOqtXr87pp5+eD3zgA/n+97+f2bNnZ/bs2XnggQd6xlx22WX5whe+kKVLl+buu+/OK17xisycOTO/+93vqj8zAAAAAACGpT3qGXzrrbf2ur9s2bIccMABue+++/JHf/RHO1znyiuvzMknn5yPf/zjSZKLL744K1asyBe/+MUsXbo0tVotixcvzgUXXJB3vvOdSZJrr702Bx54YG666aa8973vrfK8AAAAAAAYpuoKtvvatGlTkmT//ffvd8yaNWsyf/78XstmzpyZm266KUny8MMPZ/369Zk+fXrP4/vss0+mTp2aNWvW7DDY7uzsTGdnZ8/9jo6OJElXV1e6urp2uv5tY/uu095a2+ltDJR6ntdA6K93vDR9q07vtrezv6vaR9R6/dtMw+n16bvPDafnNpAaNWf3tW3dgdiv6zXU9xX7dDX6Vp3eVaNv1ehXNc2ar3dVycdBqbWXWndSbu2l1p2ofTCUWnfSu/ZG1d9Sq9UqfSLs7u7OO97xjmzcuDF33XVXv+P23HPPXHPNNTn99NN7ln3pS1/KokWLsmHDhqxevTpvectbsm7duhx00EE9Y97znvekpaUl119//XbbXLhwYRYtWrTd8uXLl2fUqFFVng4AvKStW7fmjDPOyKZNmzJ69OjBLqcY5mwABpL5uhrzNQADqVHzdeUzts8+++w88MADLxlqN8v555/f6yzwjo6OTJgwISeddFJdzejq6sqKFSsyY8aMtLW19Sw/cuFtDa23ER5YOHOwS+ilv97x0vStOr3b3s7+rmofUcvFx3XnwntHpLO7pak1DbXfVbui7z637cwl6tOoObuvba/PQOzX9Rrqx4Hfp9XoW3V6V42+VWO+rqZZ8/WuKvk4KLX2UutOyq291LoTtQ+GUutOetf+zDPPNGSblYLtefPm5V//9V9z55135jWvec1Ljh07dmw2bNjQa9mGDRsyduzYnse3LXvxGdsbNmzIlClTdrjN9vb2tLe3b7e8ra2t0ovad73O54fWB+QkQ3Znrdrz3Z2+Vad3v1fv76rO7pam/34bjq/Ntn1uOD63gdDoObuvgdiv61XKvmK/rkbfqtO7avStPnpVTbPn6101VOqootTaS607Kbf2UutO1D4YSq07eaH25557riHbGlHP4Fqtlnnz5uXGG2/Mv//7v2fSpEkvu860adOycuXKXstWrFiRadOmJUkmTZqUsWPH9hrT0dGRu+++u2cMAAAAAABsU9cZ22effXaWL1+em2++OXvvvXfWr1+f5IUve9xrr72SJGeeeWbGjx+fSy65JElyzjnn5O1vf3suv/zyzJo1K9ddd13uvffefOUrX0mStLS05CMf+Ug+9alP5dBDD82kSZNy4YUXZty4cZk9e3YDnyoAAAAAAMNBXcH2l7/85STJCSec0Gv517/+9bz//e9Pkjz66KMZMeL3J4L/f//f/5fly5fnggsuyCc+8Ykceuihuemmm3LkkUf2jDn33HOzZcuW/NVf/VU2btyYt771rbn11lszcuTIik8LAAAAAIDhqq5gu1arveyYVatWbbfs3e9+d9797nf3u05LS0s++clP5pOf/GQ95QAAAAAAsBuq6xrbAAAAAAAw2ATbAAAAAAAURbANAAAAAEBRBNsAAAAAABRFsA0AAAAAQFEE2wAAAAAAFEWwDQAAAABAUQTbAAAAAAAURbANAAAAAEBRBNsAAAAAABRlj8EuAODFJp53yw6Xt7fWctnxyZELb0vn8y0DXBUAAAAAQ4kztgEAAAAAKIpgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAACiKYBsAAAAAgKIItgEAAAAAKIpgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAACiKYBsAAAAAgKIItgEAAAAAKIpgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAAChK3cH2nXfemVNPPTXjxo1LS0tLbrrpppcc//73vz8tLS3b3d70pjf1jFm4cOF2jx9xxBF1PxkAAAAAAIa/uoPtLVu2ZPLkyVmyZMlOjb/yyivz+OOP99x+9atfZf/998+73/3uXuPe9KY39Rp311131VsaAAAAAAC7gT3qXeGUU07JKaecstPj99lnn+yzzz4992+66ab893//d+bOndu7kD32yNixY+stBwAAAACA3Uzdwfau+trXvpbp06fnta99ba/lP/vZzzJu3LiMHDky06ZNyyWXXJKDDz54h9vo7OxMZ2dnz/2Ojo4kSVdXV7q6una6lm1j+67T3lrb6W0MlHqe10Dor3e8NH17ef0df+0jar3+ZecNZO+G077d93gd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2y1va2ur9KL2Xa/z+aH1ATnJkN1Zq/Z8d6dv/Xu546+zu2VIHqMlGIjeDcf9etvxOhyf20Bo9Jzd11D8nVDKvmK/rkbfqtO7avStPnpVTbPn6101VOqootTaS607Kbf2UutO1D4YSq07eaH25557riHbGtGQrdRp7dq1Oeigg5Ike+65Z4499tisXLmy5/Hu7u6sXLmy1xncAAAAAACQVDhje/PmzXnooYd67j/88MNZu3Zt9t9//xx88ME5//zz89hjj+Xaa69NkixevDiTJk3Km970pvzud7/LV7/61fz7v/97br/99p5tzJ8/P3PmzMlxxx2X448/PosXL86WLVsyd+7cBjxFAAAAAACGk7qD7XvvvTcnnnhiz/358+cnSebMmZNly5bl8ccfz6OPPtrz+LPPPpv//b//dx577LGMGjUqRx99dL773e/22sZpp52WJ598MhdddFHWr1+fKVOm5NZbb82BBx64K88NAAAAAIBhqO5g+4QTTshLfd/ksmXLet0/99xzc+65577sdufNm5d58+bVWw4AAAAAALuZQbnGNgAAAAAAVCXYBgAAAACgKIJtAAAAAACKItgGAAAAAKAogm0AAAAAAIoi2AYAAAAAoCiCbQAAAAAAiiLYBgAAAACgKIJtAAAAAACKItgGAAAAAKAogm0AAAAAAIoi2AYAAAAAoCiCbQAAAAAAiiLYBgAAAACgKIJtAAAAAACKItgGAAAAAKAogm0AAAAAAIoi2AYAAAAAoCiCbQAAAAAAiiLYBgAAAACgKIJtAAAAAACKItgGAAAAAKAogm0AAAAAAIoi2AYAAAAAoCiCbQAAAAAAiiLYBgAAAACgKIJtAAAAAACKItgGAAAAAKAogm0AAAAAAIpSd7B955135tRTT824cePS0tKSm2666SXHf+tb38qMGTPy6le/OqNHj860adNy22239RqzcOHCtLS09LodccQR9ZYGAAAAAMBuoO5ge8uWLZk8eXKWLFmyU+PvvPPOzJgxI9/5zndy33335cQTT8ypp56a73//+73GvelNb8rjjz/ec7vrrrvqLQ0AAAAAgN3AHvWucMopp+SUU07Z6fGLFy/udf/Tn/50br755nz729/OMccc8/tC9tgjY8eO3altdnZ2prOzs+d+R0dHkqSrqytdXV07Xdu2sX3XaW+t7fQ2Bko9z2sg9Nc7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uHFj3vrWt+bWW2/NyJEjG/AUAQAAAAAYTuoOtpNk3rx5mTdv3g4fW7VqVa/7n//85/P5z3/+JbfX0tKST37yk/nkJz9ZpRwAAAAAAHYjdV1jGwAAAAAABptgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAACiKYBsAAAAAgKIItgEAAAAAKIpgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAACiKYBsAAAAAgKIItgEAAAAAKIpgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAACiKYBsAAAAAgKIItgEAAAAAKIpgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAACiKYBsAAAAAgKIItgEAAAAAKIpgGwAAAACAogi2AQAAAAAoimAbAAAAAICiVAq2lyxZkokTJ2bkyJGZOnVq7rnnnn7HXnXVVXnb296W/fbbL/vtt1+mT5++3fj3v//9aWlp6XU7+eSTq5QGAAAAAMAwV3ewff3112f+/PlZsGBB7r///kyePDkzZ87ME088scPxq1atyumnn57/+I//yJo1azJhwoScdNJJeeyxx3qNO/nkk/P444/33L7xjW9Ue0YAAAAAAAxre9S7whVXXJGzzjorc+fOTZIsXbo0t9xyS66++uqcd955243/p3/6p173v/rVr+Zf/uVfsnLlypx55pk9y9vb2zN27Nh6ywEAAACoy8Tzbmnq9ttba7ns+OTIhbel8/mWnVrnkUtnNbUmgOGmrmD72WefzX333Zfzzz+/Z9mIESMyffr0rFmzZqe2sXXr1nR1dWX//ffvtXzVqlU54IADst9+++WP//iP86lPfSqvetWrdriNzs7OdHZ29tzv6OhIknR1daWrq2unn8+2sX3XaW+t7fQ2Bko9z2sgbKunfYRe1aO/fY7f6+/427avDcV9bqjb3XtX9Xjre7w6bqtp1Jzd11Ceh4aiF/faPl2NvlWnd9XoWzX6VU2z5utd1czjoNmf+6u8Bx8K+2/Jv3tKrb3UuhO1D4ZS6056196o+ltqtdpO/5Zdt25dxo8fn9WrV2fatGk9y88999zccccdufvuu192Gx/+8Idz22235Uc/+lFGjhyZJLnuuusyatSoTJo0KT//+c/ziU98Iq985SuzZs2atLa2breNhQsXZtGiRdstX758eUaN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5uY2GLddna/r+sPNU089leeffz4HHnhgr+UHHnhgfvKTn+xwnfXr1+9w/Pr163se37asvzF9nX/++Zk/f37P/e7u7jz99NN51atelZaWnf9rRUdHRyZMmJBf/epXGT169E6vh95VpW/V6V11eldN377VarX89re/zbhx4wa7tKI0as7uy35dnd5Vo2/V6V01+laN+bqaZs3Xu6rk46DU2kutOym39lLrTtQ+GEqtO+ld+957792Q+bqZ/yOhadrb29Pe3t5r2b777lt5e6NHjy5uZxgq9K4afatO76rTu2pe3Ld99tlnkKspT6Pn7L7s19XpXTX6Vp3eVaNv9TNf16/Z8/WuKvk4KLX2UutOyq291LoTtQ+GUutOfl97I+brEfUMHjNmTFpbW7Nhw4Zeyzds2JCxY8fucJ2xY8e+5Pht/9azTQAAAAAAdl91Bdt77rlnjj322Kxc+fsL4nd3d2flypWZNm3aDteZNm1ar/FJsmLFip7xkyZNytixY3uN6ejoyN13393vNgEAAAAA2H3VfSmS+fPnZ86cOTnuuONy/PHHZ/HixdmyZUvmzp2bJDnzzDMzfvz4XHLJJUmSc845J29/+9tz+eWXZ9asWbnuuuty77335itf+UqSpKWlJR/5yEfyqU99KoceemgmTZqUCy+8MOPGjcvs2bMb90x3oL29PQsWLNjuv1zx8vSuGn2rTu+q07tq9G1o8/pUp3fV6Ft1eleNvkHZx0GptZdad1Ju7aXWnah9MJRad9Kc2ltqtVqt3pW++MUv5rOf/WzWr1+fKVOm5Atf+EKmTp2aJDnhhBMyceLELFu2rGf8DTfckAsuuCCPPPJIDj300Fx22WX50z/9057Ha7VaFixYkK985SvZuHFj3vrWt+ZLX/pSDjvssF1/hgAAAAAADCuVgm0AAAAAABgsdV1jGwAAAAAABptgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAACiKYBsAAAAAgKIItgEAAAAAKIpgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAACiKYBsAAAAAgKIItgEAAAAAKIpgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAACiKYBsAAAAAgKIItgEAAAAAKIpgGwAAAACAogi2AQAAAAAoimAbAAAAAICiCLYBAAAAACiKYBsA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4//pn/4pH/7whzNlypQcccQR+epXv5ru7u6sXLmyGeUBAAAAAFCwhn955LPPPpv77rsv559/fs+yESNGZPr06VmzZs1ObWPr1q3p6urK/vvvv8PHOzs709nZ2XO/o6MjSdLV1ZWurq6drnXb2HrWqaK9tdbU7Q+G9hEvPKdm9264Gah9bjjSu+r0rpq+fdO/aho1Z/fldamuEb1r9nubofi61tu33bFH/XG8VrPt/fa2f5thOL4mw/E5DYT+5msAGMpaarVaQ98prVu3LuPHj8/q1aszbdq0nuXnnntu7rjjjtx9990vu40Pf/jDue222/KjH/0oI0eO3O7xhQsXZtGiRdstX758eUaNGrVrTwAAdmDr1q0544wzsmnTpowePXqwyymGORuAgWS+rqa/+VofAWiGjo6O7LPPPrs8zwy5YPvSSy/NZZddllWrVuXoo4/e4Zgd/TV5woQJeeqpp+pqRldXV1asWJEZM2akra1tp9er15ELb2vatgdL+4haLj6uu+m9G24Gap8bjvSuOr2rpm/fOjo6MmbMGB/w6tSoObsv+3V1jehds9/bPLBwZlO3X0W9fdsde9Qfx2s1x37y1lx8XHcuvHdEOrtbmvIzStqPdpb5upr+5mt9BKAZGhVsN/xSJGPGjElra2s2bNjQa/mGDRsyduzYl1z3c5/7XC699NJ897vf7TfUTpL29va0t7dvt7ytra3Sm+Wq6+2szueb80Z0KGh274YrfatO76rTu2q29U3vqmn0nN2s7eyOdqV3zX5vM5Rf053t2+7co/44XuuzLczu7G5p2v40HF+P4ficBkJ/8zUADGUN//LIPffcM8cee2yvL37c9kWQLz6Du6/LLrssF198cW699dYcd9xxjS4LAAAAAIBhouFnbCfJ/PnzM2fOnBx33HE5/vjjs3jx4mzZsiVz585Nkpx55pkZP358LrnkkiTJZz7zmVx00UVZvnx5Jk6cmPXr1ydJXvnKV+aVr3xlM0oEAAAAAKBQTQm2TzvttDz55JO56KKLsn79+kyZMiW33nprDjzwwCTJo48+mhEjfn+y+Je//OU8++yzede73tVrOwsWLMjChQubUSIAAAAAAIVqSrCdJPPmzcu8efN2+NiqVat63X/kkUeaVQYAAAAAAMNMw6+xDQAAAAAAzSTYBgAAAACgKE27FAkAAEB/Jp53S1O3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YX8T6dtZqrmTkpKwbNkyAEBubi76+/tx4MAB3HfffbGMG0XnvpvJ9TIhJSUFa9asweDgYCwiTknnnpzJzK9cuYLGxkbs3bs3lhGnpWvPquRevHgxvvjiC/zzzz+4ePEiMjIyUFFRgezs7JjnnanZuMdO2Fc/drsda9euRVtbW+TY+Pg42tra4Ha7p3yO2+2OWg8AX3/99bTrE5XK7ADg9ddfx759+9DS0oJ169aZETWuGJ3b8uXL0dPTA5/PF3k8/PDDuP/+++Hz+ZCVlWVmfEupXHMbNmzA4OBg5IUVAPz8889wuVxz4oZ4gsrsrl69OqmUJ140/fc7Hmgq7AjzsMPVscPVsMPVscPVsL+J9O0s1dzXGh8fRygUikXEaencd7Mx97GxMfT09MDlcsUq5iQ69+RMZv7ZZ58hFArh0UcfjXXMKenaszOZucPhQGZmJsLhMI4dO4bi4uJYx52xWbmHM/SVlpppbGyU1NRU+eijj6Svr0927twpN910kwSDQRER8Xg8UlFREVn//fffS3JystTW1kp/f79UVVVJSkqK9PT0WHUKljE6u5qaGrHb7fL555/LuXPnIo/h4WGrTsESRud2LSu/YdpqRmf322+/idPplN27d8vAwIB89dVXkpaWJq+99ppVp2AZo7OrqqoSp9Mpn3zyiQQCAWltbZWcnBzZunWrVadgieHhYenu7pbu7m4BIAcPHpTu7m759ddfRUSkoqJCPB5PZH0gEJD58+fL888/L/39/XL48GGx2WzS0tJi1SkkNHa4Ona4Gna4Ona4GvY3kb6dZTR3dXW1tLa2it/vl76+PqmtrZXk5GQ5cuSIqblVsl/Lyr4zmv3VV1+VEydOiN/vl87OTtm2bZs4HA7p7e2N69zx1JOq18vGjRulpKTE7LhRdO1Zo7lPnz4tx44dE7/fL999951s3rxZ7rjjDvnzzz9NzS1izT12Qm9si4gcOnRIbr/9drHb7ZKXlyenT5+O/KygoEBKS0uj1n/66ady1113id1ul5UrV0pTU5PJieOHkdktXbpUAEx6VFVVmR/cYkavuf81l2+KRYzP7ocffpD169dLamqqZGdny/79+yUcDpucOj4Ymd3o6Ki88sorkpOTIw6HQ7KysmTXrl2WFJ+VTp48OeX/WxOzKi0tlYKCgknPyc3NFbvdLtnZ2dLQ0GB67rmEHa6OHa6GHa6OHa6G/U2kb2cZyV1ZWSnLli0Th8MhN998s7jdbmlsbDQ98wSd+85I9meffTayNj09XYqKiqSrq8uC1Hr3pNHsZ8+eFQDS2tpqctLJdO1ZI7nb29tlxYoVkpqaKrfeeqt4PB75448/TM8sYs099g0i/Ls1IiIiIiIiIiIiItJHwn7GNhERERERERERERElJm5sExEREREREREREZFWuLFNRERERERERERERFrhxjYRERERERERERERaYUb20RERERERERERESkFW5sExEREREREREREZFWuLFNRERERERERERERFrhxjYRERERERERERERaYUb20RERERERERERESkFW5sExEREREREREREZFWuLFNRERERERERERERFr5D6MB2v+gUHmIAAAAAElFTkSuQmCC", + "image/png": "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x44dhXOampoil8vFpEmTCue8+zm6zul6DgAAAAAA+p+iyuw/+ZM/iQ0bNsR//ud/xsaNG+PTn/50DBgwID772c/GsGHD4oorroj58+fH3//938fmzZvj8ssvj7q6upg6dWpERMycOTMmTZoUl112WfzTP/1TrFmzJm688cZobGws/BTx6quvjv/4j/+IG264If71X/817rrrrnjooYdi3rx5pZ8eAAAAAIBMKOpjRv77v/87PvvZz8Yvf/nLeN/73hcf/ehHY9OmTfG+970vIiKWLVsW1dXVMXv27Ghvb4/6+vq46667Co8fMGBAPPbYY3HNNddEXV1dHH300TFnzpy4+eabC+dMmDAhVq9eHfPmzYs77rgjxo4dG/fcc0/U19eXaGQAAAAAALKmqDL7gQceOOT9gwYNiuXLl8fy5csPes748ePj8ccfP+TzTJs2LV544YViogEAAAAA0Icd1mdmAwAAAABAOSizAQAAAABInjIbAAAAAIDkKbMBAAAAAEieMhsAAAAAgOQpswEAAAAASJ4yGwAAAACA5CmzAQAAAABInjIbAAAAAIDkKbMBAAAAAEieMhsAAAAAgOQpswEAAAAASJ4yGwAAAACA5CmzAQAAAABInjIbAAAAAIDkKbMBAAAAAEieMhsAAAAAgOQpswEAAAAASJ4yGwAAAACA5CmzAQAAAABInjIbAAAAAIDkKbMBAAAAAEieMhsAAAAAgOQpswEAAAAASJ4yGwAAAACA5CmzAQAAAABInjIbAAAAAIDkKbMBAAAAAEieMhsAAAAAgOQpswEAAAAASJ4yGwAAAACA5B1Wmf3Vr341qqqqYu7cuYVjb731VjQ2NsaIESPimGOOidmzZ0dra2u3x23bti0aGhpiyJAhMXLkyLj++uvj7bff7nbO+vXr46yzzora2to46aSTYuXKlYcTFQAAAACADOt1mf3cc8/FN7/5zTj99NO7HZ83b148+uij8fDDD8eGDRti+/btcdFFFxXu37t3bzQ0NMSePXti48aNcf/998fKlStj4cKFhXNeeeWVaGhoiPPPPz+2bNkSc+fOjSuvvDLWrFnT27gAAAAAAGRYr8rsN954Iy699NL49re/Hccee2zh+K5du+Lee++N2267LS644IKYMmVK3HfffbFx48bYtGlTRESsXbs2Xnrppfirv/qrmDx5csyaNSuW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ndm9UVtbG7W1td2ODR8+vDJhipTL5ZJ+MR0Os2VXX54v9dn66jtGupR6X6d+PQ8ki5kj5C43ucurN7nt657rT6+LFMhdXnKXl329P31ImsyWTWarrJ7s6yR/LHn88cfHgAEDorW1tdvx1tbWGD16dIVSAQAAAABQKUmW2QMHDowpU6bEunXrCsc6Ojpi3bp13T52BAAAAACA/iHZjxmZP39+zJkzJ84+++w455xz4vbbb48333wzLr/88kpHK5na2tq46aab9vvjQH2B2bKrL8/Xl2frj7J4PbOYOULucpO7vLKaOyuy+v2Vu7zkLi+5SUFfvp5myyazZUeSfwFklzvvvDO+/vWvR0tLS0yePDm+8Y1vxLnnnlvpWAAAAAAAlFnSZTYAAAAAAEQk+pnZAAAAAADwbspsAAAAAACSp8wGAAAAACB5ymwAAAAAAJKnzC6z1157LS699NLI5XIxfPjwuOKKK+KNN97o0WM7Oztj1qxZUVVVFd/73veObNBeKHa21157La677rr40Ic+FIMHD44TTzwx/uiP/ih27dpVxtQHtnz58vjABz4QgwYNinPPPTeeffbZQ57/8MMPx8knnxyDBg2K0047LR5//PEyJe2dYub79re/HR/72Mfi2GOPjWOPPTamT5/+nt+PSir22nV54IEHoqqqKj71qU8d2YD0WG/25bRp06Kqqqrb7eqrr+52zrZt26KhoSGGDBkSI0eOjOuvvz7efvvtiuXu6S7cd66qqqp44IEHep2z1Huus7MzFi5cGCeccEIMHjw4pk+fHj/5yU96na8UuXuyv/7X//pf+31ff+u3fquiuVeuXLlfpkGDBnU7J8Xv94H+/1dVVRUNDQ2Fc4709/upp56KT3ziEzFmzJge//vS+vX///buP6aq+v8D+Esu3JsoF2JXQagYqLESVFyDYCmbMIGs2HRLcHPYmrayVWYmVGZBW5jM1hz1cQbYHy3mmqZbis6ULdzV0q56BXRdvFY2Lk2sCwgZF57fP/py4/L7nnvvuRd8PjamnHvu8fl+c+8TebU89bJkyRLR6XQyb9482b9//7BzlH5vuVewr9nXnuZmX6ubm31N/sJ5iOv5nIeoj7OQ4SblLASkqtzcXCxatAhnz57F999/j3nz5qGwsHBCz929ezfy8vIgIjh06JBvgyrg7trMZjNWrVqFI0eOwGKx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qak4e/as87HMzEwUFRW5nH/gwAE8/PDD0Gq1WLBgAb799luVE7vHnfXFxcWN+DXasWOH+sEnwN2v3WCTrcCnOnc75ddff8WyZcsQGRkJnU6HefPmYevWrbDb7S7XvXHjBvLy8jB9+nQYDAZs2bIFvb29fss9kS48duwYFi9ejJkzZ2LGjBlYtGgR/ve//6Gvr09xTm/3XH9/P7Zv346oqCjodDpkZWXh2rVrivN5I/d4/dXd3Y0VK1Zg1qxZCAkJQVxcHDZs2OCTH3rdyf3aa685z42KisKTTz6Jn376yeV6gbjfAHD16lWICE6cODHsWmrs92jvp4GcRUVFyMzMHPacxYsXQ6vVIiEhATU1NcOuO9Y+EPuafe15bva1urkB9jX5B+chnIf4G2chI5tss5BpAODeP0xCRERERERERERERKSuIH8HICIiIiIiIiIiIiIaD4fZRERERERERERERBTwOMwmIiIiIiIiIiIiooDHYTYRERERERERERERBTwOs4mIiIiIiIiIiIgo4HGYTUREREREREREREQBj8NsIiIiIiIiIiIiIgp4HGYTERERERERERERUcDjMJuIiIiIiIiIiIiIAh6H2UREREREREREREQU8DjMJiIiIiIiIiIiIqKA938OvMOUf0SRUwAAAABJRU5ErkJggg==", "text/plain": [ - "
" + "
" ] }, "metadata": {}, @@ -612,12 +650,12 @@ "# plot a histogram of ending risk levels for each run\n", "# combine into a grid of plots\n", "import matplotlib.pyplot as plt\n", - "fig, ax = plt.subplots(ncols=3, nrows=2, sharex='col', sharey='row', figsize=(18,10))\n", + "fig, ax = plt.subplots(ncols=4, nrows=3, sharex='col', sharey='row', figsize=(18,10))\n", "\n", "for run in last_step.RunId.unique():\n", " run_last_step = last_step[last_step.RunId == run]\n", - " plot_location = ax[int(run/3), int(run % 3)]\n", - " run_last_step.risk_level.hist(ax=plot_location, bins=10)" + " plot_location = ax[int(run/4), int(run % 4)]\n", + " run_last_step.risk_level.hist(ax=plot_location, bins=11)" ] } ], From 3df943b4aa52e29544934215215b8da6f1149aee Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 25 Jul 2023 15:23:42 -0400 Subject: [PATCH 050/141] Update batch run test to match change in batch run params --- tests/test_batch_run.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_batch_run.py b/tests/test_batch_run.py index 69f9a2a..ba21cf8 100644 --- a/tests/test_batch_run.py +++ b/tests/test_batch_run.py @@ -39,9 +39,9 @@ def test_riskyfood_batch_run(mock_batch_run): results = riskyfood_batch_run() mock_batch_run.assert_called_with( RiskyFoodModel, - parameters={"n": 10}, - iterations=5, - max_steps=22, + parameters={"n": 110, "mode": "types"}, + iterations=10, + max_steps=100, number_processes=1, # set None to use all available; set 1 for jupyter data_collection_period=1, display_progress=True, From b47ba17c75bfe4e03ce009dfcddecd7732e3b8c2 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 25 Jul 2023 15:38:45 -0400 Subject: [PATCH 051/141] Update for Mesa 2.1 --- pyproject.toml | 2 +- simulatingrisk/risky_bet/model.py | 11 +++++++---- simulatingrisk/risky_food/model.py | 4 ++-- tests/test_risky_bet.py | 2 +- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d5d2058..27ec664 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ classifiers = [ "Programming Language :: Python :: 3", ] dependencies = [ - "mesa", + "mesa>=2.1", ] dynamic = ["version", "readme"] diff --git a/simulatingrisk/risky_bet/model.py b/simulatingrisk/risky_bet/model.py index 95fe43f..88dc755 100644 --- a/simulatingrisk/risky_bet/model.py +++ b/simulatingrisk/risky_bet/model.py @@ -168,7 +168,10 @@ def step(self): # every ten rounds, agents adjust their risk level # delete cached property before the next round - del self.agent_risk_levels + try: + del self.agent_risk_levels + except AttributeError: + pass def call_risky_bet(self): # flip a weighted coin to determine if the risky bet pays off, @@ -189,17 +192,17 @@ def agent_risk_levels(self) -> [float]: # property is cached but should be cleared in each new round # NOTE: occasionally median method is complaining that this is empty - return [a.risk_level for a in self.schedule.agent_buffer()] + return [a.risk_level for a in self.schedule.agents] @property def max_agent_wealth(self): # what is the current largest wealth of any agent? - return max([a.wealth for a in self.schedule.agent_buffer()]) + return max([a.wealth for a in self.schedule.agents]) @property def risk_median(self): # calculate median of current agent risk levels - if not any(self.agent_risk_levels): + if not self.agent_risk_levels: # occasionally this complains about an empty list # hopefully only possible in unit tests... return diff --git a/simulatingrisk/risky_food/model.py b/simulatingrisk/risky_food/model.py index e16d7fb..416ebcb 100644 --- a/simulatingrisk/risky_food/model.py +++ b/simulatingrisk/risky_food/model.py @@ -128,7 +128,7 @@ def propagate(self): # get a generator of agents from the scheduler that # will allow us to add and remove - for agent in self.schedule.agent_buffer(): + for agent in self.schedule.agents: # add offspring based on payoff; keep risk level # logic is offspring = to payoff, original dies off, # but for efficiency just add payoff - 1 and keep the original @@ -168,7 +168,7 @@ def agents(self): # uses a generator of agents from the scheduler that # will allow adding and removing agents from the scheduler - return self.schedule.agent_buffer() + return self.schedule.agents @property def total_agents(self): diff --git a/tests/test_risky_bet.py b/tests/test_risky_bet.py index 907f27d..5abe874 100644 --- a/tests/test_risky_bet.py +++ b/tests/test_risky_bet.py @@ -56,7 +56,7 @@ def test_gambler_neighbors(): # every agent should have 4 neighbors, # even if they are on the edge of the grid - for agent in model.schedule.agent_buffer(): + for agent in model.schedule.agents: assert len(agent.neighbors) == 4 From 58040e0b413d327ecb05b1078d09ee83fc4e9b53 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 25 Jul 2023 18:09:05 -0400 Subject: [PATCH 052/141] Add tests for risky food model --- simulatingrisk/risky_bet/model.py | 5 +-- simulatingrisk/risky_food/model.py | 19 ++++++--- tests/test_risky_food.py | 67 +++++++++++++++++++++++++++++- 3 files changed, 82 insertions(+), 9 deletions(-) diff --git a/simulatingrisk/risky_bet/model.py b/simulatingrisk/risky_bet/model.py index 88dc755..54092a8 100644 --- a/simulatingrisk/risky_bet/model.py +++ b/simulatingrisk/risky_bet/model.py @@ -202,11 +202,10 @@ def max_agent_wealth(self): @property def risk_median(self): # calculate median of current agent risk levels - if not self.agent_risk_levels: + if self.agent_risk_levels: # occasionally this complains about an empty list # hopefully only possible in unit tests... - return - return statistics.median(self.agent_risk_levels) + return statistics.median(self.agent_risk_levels) @property def risk_mean(self): diff --git a/simulatingrisk/risky_food/model.py b/simulatingrisk/risky_food/model.py index 416ebcb..ae7c5ce 100644 --- a/simulatingrisk/risky_food/model.py +++ b/simulatingrisk/risky_food/model.py @@ -25,17 +25,25 @@ def __init__(self, unique_id, model, risk_level=None): if risk_level is None: # only set randomly if None; allow zero risk risk_level = self.random.random() self.risk_level = risk_level + self.choice = None def __repr__(self): return f"" def step(self): # choose food based on the probability not contaminated and risk tolerance - if self.risk_level > self.model.prob_notcontaminated: - choice = FoodChoice.RISKY + # lower risk level = risk seeking + # higher = risk averse + + # FIXME: confirm with Lara if this should be r > p or p > r + # risky bet uses p > r + # if self.risk_level > self.model.prob_notcontaminated: + if self.model.prob_notcontaminated > self.risk_level: + self.choice = FoodChoice.RISKY else: - choice = FoodChoice.SAFE - self.payoff = self.model.payoff(choice) + # test: risk level 1.0 should always choose safe (not strictly greater) + self.choice = FoodChoice.SAFE + self.payoff = self.model.payoff(self.choice) class RiskyFoodModel(mesa.Model): @@ -143,9 +151,10 @@ def propagate_types(self): # adjust population based on payoff and number of agents total = len(agents) # calculate number of agents of this type for next round + # - convert to int so we can use for array slicing new_total = int((total * agents[0].payoff) / 2) - # if new total is less, remove agents over the needed total + # if new total is less, remove agents over the expected total for agent in agents[new_total:]: self.schedule.remove(agent) # if new total is more, add new agents with same risk level diff --git a/tests/test_risky_food.py b/tests/test_risky_food.py index 83a464a..41a9978 100644 --- a/tests/test_risky_food.py +++ b/tests/test_risky_food.py @@ -1,9 +1,15 @@ from collections import Counter import math +from unittest.mock import Mock, patch, PropertyMock import pytest -from simulatingrisk.risky_food.model import RiskyFoodModel, FoodStatus, Agent +from simulatingrisk.risky_food.model import ( + RiskyFoodModel, + FoodStatus, + FoodChoice, + Agent, +) test_probabilities = [ @@ -57,3 +63,62 @@ def test_agent_init(): # allow zero risk (should not get a random value) agent0 = Agent(1, model, risk_level=0) assert agent0.risk_level == 0 + + +def test_agent_step(): + model = RiskyFoodModel(1) + agent = model.schedule.agents[0] + # if risk level is lower than probability not contaminated, agent will risk + agent.risk_level = 0.2 + model.prob_notcontaminated = 0.3 + model.risky_food_status = FoodStatus.CONTAMINATED + agent.step() + assert agent.choice == FoodChoice.RISKY + + # if not strictly higher, no risk + model.prob_notcontaminated = 0.2 + agent.step() + assert agent.choice == FoodChoice.SAFE + + # risk level 1.0 == always chooses safe + agent.risk_level = 1.0 + model.prob_notcontaminated = 0.99 + agent.step() + assert agent.choice == FoodChoice.SAFE + + +def test_propagate_types(): + model = RiskyFoodModel(mode="types") + # patch in schedule and agents by risk type + model.schedule = Mock() + with patch.object( + RiskyFoodModel, "agents_by_risktype", new_callable=PropertyMock + ) as mock_agents_by_rtype: + # simulate safe payoff (2) + mock_agents_by_rtype.return_value = { + 0.3: [Mock(payoff=2), Mock(), Mock(), Mock()] + } + model.propagate_types() + # total should stay the same; no agents removed or added + model.schedule.remove.assert_not_called() + model.schedule.add.assert_not_called() + + # simulate contaminated food payoff (1) + mock_agents_by_rtype.return_value = { + 0.2: [Mock(payoff=1), Mock(), Mock(), Mock()] + } + model.schedule.reset_mock() + model.propagate_types() + # population should be cut in half; should remove two agents + assert model.schedule.remove.call_count == 2 + model.schedule.add.assert_not_called() + + # simulate non-contaminated risky food payoff (4) + mock_agents_by_rtype.return_value = { + 0.6: [Mock(payoff=4), Mock(), Mock(), Mock()] + } + model.schedule.reset_mock() + model.propagate_types() + # population should double; should add four agents + assert model.schedule.add.call_count == 4 + model.schedule.remove.assert_not_called() From c06564664ec45329e9024faf91e71af97dba46ad Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Thu, 27 Jul 2023 18:30:35 -0400 Subject: [PATCH 053/141] Confirmed change in p > r comparison with @LaraBuchak --- simulatingrisk/risky_food/model.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/simulatingrisk/risky_food/model.py b/simulatingrisk/risky_food/model.py index ae7c5ce..fe77ca6 100644 --- a/simulatingrisk/risky_food/model.py +++ b/simulatingrisk/risky_food/model.py @@ -34,14 +34,10 @@ def step(self): # choose food based on the probability not contaminated and risk tolerance # lower risk level = risk seeking # higher = risk averse - - # FIXME: confirm with Lara if this should be r > p or p > r - # risky bet uses p > r - # if self.risk_level > self.model.prob_notcontaminated: + # risk level 1.0 should always choose safe (not strictly greater) if self.model.prob_notcontaminated > self.risk_level: self.choice = FoodChoice.RISKY else: - # test: risk level 1.0 should always choose safe (not strictly greater) self.choice = FoodChoice.SAFE self.payoff = self.model.payoff(self.choice) From c75dda60dd2f12e12ca8be2cec2825ef2b158c51 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Thu, 27 Jul 2023 18:36:40 -0400 Subject: [PATCH 054/141] Add notes to the readme about the project and how we define risk level Co-authored-by: Lara Buchak <140551577+LaraBuchak@users.noreply.github.com> --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 7001dd2..0a2a3aa 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # Simulating Risk +The code in this repository is associated with the CDH project [Simulating risk, risking simulations](https://cdh.princeton.edu/projects/simulating-risk/). + +Simulations are implemented with [Mesa](https://mesa.readthedocs.io/en/stable/), using Agent Based Modeling to explore risk attitudes within populations. + +Across simulations, we define agents with risk attitudes tracked via a numeric `r` or `risk_level` 0.0 - 1.0, where `r` is that agent's minimum acceptable risk level. An agent with `r=1` will always take the safe option (no risk is acceptable); an agent with `r=0` will always take the risky choice (any risk is acceptable). + ## Development instructions Initial setup and installation: From e018ef0e1091ee9393cb9039eb7830df2442f7f6 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Thu, 3 Aug 2023 14:31:24 -0400 Subject: [PATCH 055/141] Tweak batch run parameters to check for convergence in current sims --- simulatingrisk/batch_run.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/simulatingrisk/batch_run.py b/simulatingrisk/batch_run.py index 58ba137..8a21330 100755 --- a/simulatingrisk/batch_run.py +++ b/simulatingrisk/batch_run.py @@ -14,11 +14,16 @@ def riskybet_batch_run(): results = batch_run( RiskyBetModel, parameters={ - "grid_size": [10, 20, 30], # 100], - "risk_adjustment": ["adopt", "average"], + "grid_size": 30, # [20, 30], # 100], + # "risk_adjustment": ["adopt", "average"], + "risk_adjustment": "adopt", }, iterations=5, - max_steps=100, + # TODO: vary how often they update strategy + # every 100, every 1 round? + max_steps=3000, # at least 1000, maybe more to see where it converges + # try 10k to see + # add logic on the model to stop if risk levels converge to 90% in one bin number_processes=1, # set None to use all available; set 1 for jupyter data_collection_period=1, display_progress=True, @@ -32,8 +37,8 @@ def riskyfood_batch_run(): RiskyFoodModel, # only parameter to this one currently is number of agents parameters={"n": 110, "mode": "types"}, - iterations=10, # this one is faster, let's run more iterations - max_steps=100, + iterations=5, # this one is faster, could run more iterations + max_steps=1000, number_processes=1, # set None to use all available; set 1 for jupyter data_collection_period=1, display_progress=True, From 304388d8e9e64930a92f50a9c7b2f499e24001a3 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Thu, 3 Aug 2023 14:32:03 -0400 Subject: [PATCH 056/141] Update analysis notebook to run against latest batch run data --- notebooks/risky_bet_batch_analysis.ipynb | 605 +++++++++++------------ 1 file changed, 278 insertions(+), 327 deletions(-) diff --git a/notebooks/risky_bet_batch_analysis.ipynb b/notebooks/risky_bet_batch_analysis.ipynb index 1a0be1f..9c907b4 100644 --- a/notebooks/risky_bet_batch_analysis.ipynb +++ b/notebooks/risky_bet_batch_analysis.ipynb @@ -2,190 +2,51 @@ "cells": [ { "cell_type": "code", - "execution_count": 33, + "execution_count": 20, "id": "fae3476d-4db4-41af-9e14-7c6720b6f70d", "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "\n", - "df = pd.read_csv(\"riskybet_2023-07-18T175453_425590.csv\")" + "df = pd.read_csv(\"../riskybet_2023-08-01T170427_097240.csv\")" ] }, { "cell_type": "code", - "execution_count": 34, + "execution_count": null, "id": "a09b3130-e43f-44a4-b161-30f43412f91a", "metadata": {}, + "outputs": [], + "source": [ + "df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "6f84281e-b7fc-4b05-8e5f-771e9f3e2da3", + "metadata": {}, "outputs": [ { "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
RunIditerationStepgrid_sizerisk_adjustmentprob_risky_payoffrisky_betrisk_minrisk_q1risk_meanrisk_q3risk_maxAgentIDrisk_levelchoice
000010adopt0.335726True0.0032840.2045240.4577050.7074010.987907NaNNaNNaN
100110adopt0.000580False0.0032840.2045240.4577050.7074010.9879070.00.185156Bet.RISKY
200110adopt0.000580False0.0032840.2045240.4577050.7074010.9879071.00.710948Bet.SAFE
300110adopt0.000580False0.0032840.2045240.4577050.7074010.9879072.00.763103Bet.SAFE
400110adopt0.000580False0.0032840.2045240.4577050.7074010.9879073.00.782158Bet.SAFE
\n", - "
" - ], "text/plain": [ - " RunId iteration Step grid_size risk_adjustment prob_risky_payoff \n", - "0 0 0 0 10 adopt 0.335726 \\\n", - "1 0 0 1 10 adopt 0.000580 \n", - "2 0 0 1 10 adopt 0.000580 \n", - "3 0 0 1 10 adopt 0.000580 \n", - "4 0 0 1 10 adopt 0.000580 \n", - "\n", - " risky_bet risk_min risk_q1 risk_mean risk_q3 risk_max AgentID \n", - "0 True 0.003284 0.204524 0.457705 0.707401 0.987907 NaN \\\n", - "1 False 0.003284 0.204524 0.457705 0.707401 0.987907 0.0 \n", - "2 False 0.003284 0.204524 0.457705 0.707401 0.987907 1.0 \n", - "3 False 0.003284 0.204524 0.457705 0.707401 0.987907 2.0 \n", - "4 False 0.003284 0.204524 0.457705 0.707401 0.987907 3.0 \n", - "\n", - " risk_level choice \n", - "0 NaN NaN \n", - "1 0.185156 Bet.RISKY \n", - "2 0.710948 Bet.SAFE \n", - "3 0.763103 Bet.SAFE \n", - "4 0.782158 Bet.SAFE " + "3000" ] }, - "execution_count": 34, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "df.head()" + "last_step_n = max(df.Step)\n", + "last_step_n" ] }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 22, "id": "549276ba-fb0a-4410-a47c-7260c3c8795f", "metadata": {}, "outputs": [ @@ -229,93 +90,93 @@ " \n", " \n", " \n", - " 9901\n", + " 2699101\n", " 0\n", " 0\n", - " 100\n", - " 10\n", + " 3000\n", + " 30\n", " adopt\n", - " 0.008986\n", + " 0.146268\n", " False\n", - " 0.628171\n", - " 0.667157\n", - " 0.742527\n", - " 0.792038\n", - " 0.987907\n", + " 0.921671\n", + " 0.932366\n", + " 0.959834\n", + " 0.989232\n", + " 0.997649\n", " 0.0\n", - " 0.763103\n", + " 0.964861\n", " Bet.SAFE\n", " \n", " \n", - " 9902\n", + " 2699102\n", " 0\n", " 0\n", - " 100\n", - " 10\n", + " 3000\n", + " 30\n", " adopt\n", - " 0.008986\n", + " 0.146268\n", " False\n", - " 0.628171\n", - " 0.667157\n", - " 0.742527\n", - " 0.792038\n", - " 0.987907\n", + " 0.921671\n", + " 0.932366\n", + " 0.959834\n", + " 0.989232\n", + " 0.997649\n", " 1.0\n", - " 0.987907\n", + " 0.962729\n", " Bet.SAFE\n", " \n", " \n", - " 9903\n", + " 2699103\n", " 0\n", " 0\n", - " 100\n", - " 10\n", + " 3000\n", + " 30\n", " adopt\n", - " 0.008986\n", + " 0.146268\n", " False\n", - " 0.628171\n", - " 0.667157\n", - " 0.742527\n", - " 0.792038\n", - " 0.987907\n", + " 0.921671\n", + " 0.932366\n", + " 0.959834\n", + " 0.989232\n", + " 0.997649\n", " 2.0\n", - " 0.987907\n", + " 0.926470\n", " Bet.SAFE\n", " \n", " \n", - " 9904\n", + " 2699104\n", " 0\n", " 0\n", - " 100\n", - " 10\n", + " 3000\n", + " 30\n", " adopt\n", - " 0.008986\n", + " 0.146268\n", " False\n", - " 0.628171\n", - " 0.667157\n", - " 0.742527\n", - " 0.792038\n", - " 0.987907\n", + " 0.921671\n", + " 0.932366\n", + " 0.959834\n", + " 0.989232\n", + " 0.997649\n", " 3.0\n", - " 0.703855\n", + " 0.989232\n", " Bet.SAFE\n", " \n", " \n", - " 9905\n", + " 2699105\n", " 0\n", " 0\n", - " 100\n", - " 10\n", + " 3000\n", + " 30\n", " adopt\n", - " 0.008986\n", + " 0.146268\n", " False\n", - " 0.628171\n", - " 0.667157\n", - " 0.742527\n", - " 0.792038\n", - " 0.987907\n", + " 0.921671\n", + " 0.932366\n", + " 0.959834\n", + " 0.989232\n", + " 0.997649\n", " 4.0\n", - " 0.987907\n", + " 0.932366\n", " Bet.SAFE\n", " \n", " \n", @@ -337,150 +198,150 @@ " ...\n", " \n", " \n", - " 1400025\n", - " 29\n", + " 13500000\n", + " 4\n", " 4\n", - " 100\n", + " 3000\n", " 30\n", - " average\n", - " 0.794222\n", + " adopt\n", + " 0.263302\n", " False\n", - " 0.362583\n", - " 0.575560\n", - " 0.628468\n", - " 0.667873\n", - " 0.999013\n", + " 0.891088\n", + " 0.906854\n", + " 0.938672\n", + " 0.985759\n", + " 0.995927\n", " 895.0\n", - " 0.940166\n", + " 0.912332\n", " Bet.SAFE\n", " \n", " \n", - " 1400026\n", - " 29\n", + " 13500001\n", + " 4\n", " 4\n", - " 100\n", + " 3000\n", " 30\n", - " average\n", - " 0.794222\n", + " adopt\n", + " 0.263302\n", " False\n", - " 0.362583\n", - " 0.575560\n", - " 0.628468\n", - " 0.667873\n", - " 0.999013\n", + " 0.891088\n", + " 0.906854\n", + " 0.938672\n", + " 0.985759\n", + " 0.995927\n", " 896.0\n", - " 0.622660\n", - " Bet.RISKY\n", + " 0.909342\n", + " Bet.SAFE\n", " \n", " \n", - " 1400027\n", - " 29\n", + " 13500002\n", " 4\n", - " 100\n", + " 4\n", + " 3000\n", " 30\n", - " average\n", - " 0.794222\n", + " adopt\n", + " 0.263302\n", " False\n", - " 0.362583\n", - " 0.575560\n", - " 0.628468\n", - " 0.667873\n", - " 0.999013\n", + " 0.891088\n", + " 0.906854\n", + " 0.938672\n", + " 0.985759\n", + " 0.995927\n", " 897.0\n", - " 0.639917\n", - " Bet.RISKY\n", + " 0.986264\n", + " Bet.SAFE\n", " \n", " \n", - " 1400028\n", - " 29\n", + " 13500003\n", + " 4\n", " 4\n", - " 100\n", + " 3000\n", " 30\n", - " average\n", - " 0.794222\n", + " adopt\n", + " 0.263302\n", " False\n", - " 0.362583\n", - " 0.575560\n", - " 0.628468\n", - " 0.667873\n", - " 0.999013\n", + " 0.891088\n", + " 0.906854\n", + " 0.938672\n", + " 0.985759\n", + " 0.995927\n", " 898.0\n", - " 0.924706\n", + " 0.986130\n", " Bet.SAFE\n", " \n", " \n", - " 1400029\n", - " 29\n", + " 13500004\n", " 4\n", - " 100\n", + " 4\n", + " 3000\n", " 30\n", - " average\n", - " 0.794222\n", + " adopt\n", + " 0.263302\n", " False\n", - " 0.362583\n", - " 0.575560\n", - " 0.628468\n", - " 0.667873\n", - " 0.999013\n", + " 0.891088\n", + " 0.906854\n", + " 0.938672\n", + " 0.985759\n", + " 0.995927\n", " 899.0\n", - " 0.663706\n", - " Bet.RISKY\n", + " 0.891088\n", + " Bet.SAFE\n", " \n", " \n", "\n", - "

14000 rows × 15 columns

\n", + "

4500 rows × 15 columns

\n", "" ], "text/plain": [ - " RunId iteration Step grid_size risk_adjustment prob_risky_payoff \n", - "9901 0 0 100 10 adopt 0.008986 \\\n", - "9902 0 0 100 10 adopt 0.008986 \n", - "9903 0 0 100 10 adopt 0.008986 \n", - "9904 0 0 100 10 adopt 0.008986 \n", - "9905 0 0 100 10 adopt 0.008986 \n", - "... ... ... ... ... ... ... \n", - "1400025 29 4 100 30 average 0.794222 \n", - "1400026 29 4 100 30 average 0.794222 \n", - "1400027 29 4 100 30 average 0.794222 \n", - "1400028 29 4 100 30 average 0.794222 \n", - "1400029 29 4 100 30 average 0.794222 \n", + " RunId iteration Step grid_size risk_adjustment \n", + "2699101 0 0 3000 30 adopt \\\n", + "2699102 0 0 3000 30 adopt \n", + "2699103 0 0 3000 30 adopt \n", + "2699104 0 0 3000 30 adopt \n", + "2699105 0 0 3000 30 adopt \n", + "... ... ... ... ... ... \n", + "13500000 4 4 3000 30 adopt \n", + "13500001 4 4 3000 30 adopt \n", + "13500002 4 4 3000 30 adopt \n", + "13500003 4 4 3000 30 adopt \n", + "13500004 4 4 3000 30 adopt \n", "\n", - " risky_bet risk_min risk_q1 risk_mean risk_q3 risk_max \n", - "9901 False 0.628171 0.667157 0.742527 0.792038 0.987907 \\\n", - "9902 False 0.628171 0.667157 0.742527 0.792038 0.987907 \n", - "9903 False 0.628171 0.667157 0.742527 0.792038 0.987907 \n", - "9904 False 0.628171 0.667157 0.742527 0.792038 0.987907 \n", - "9905 False 0.628171 0.667157 0.742527 0.792038 0.987907 \n", - "... ... ... ... ... ... ... \n", - "1400025 False 0.362583 0.575560 0.628468 0.667873 0.999013 \n", - "1400026 False 0.362583 0.575560 0.628468 0.667873 0.999013 \n", - "1400027 False 0.362583 0.575560 0.628468 0.667873 0.999013 \n", - "1400028 False 0.362583 0.575560 0.628468 0.667873 0.999013 \n", - "1400029 False 0.362583 0.575560 0.628468 0.667873 0.999013 \n", + " prob_risky_payoff risky_bet risk_min risk_q1 risk_mean \n", + "2699101 0.146268 False 0.921671 0.932366 0.959834 \\\n", + "2699102 0.146268 False 0.921671 0.932366 0.959834 \n", + "2699103 0.146268 False 0.921671 0.932366 0.959834 \n", + "2699104 0.146268 False 0.921671 0.932366 0.959834 \n", + "2699105 0.146268 False 0.921671 0.932366 0.959834 \n", + "... ... ... ... ... ... \n", + "13500000 0.263302 False 0.891088 0.906854 0.938672 \n", + "13500001 0.263302 False 0.891088 0.906854 0.938672 \n", + "13500002 0.263302 False 0.891088 0.906854 0.938672 \n", + "13500003 0.263302 False 0.891088 0.906854 0.938672 \n", + "13500004 0.263302 False 0.891088 0.906854 0.938672 \n", "\n", - " AgentID risk_level choice \n", - "9901 0.0 0.763103 Bet.SAFE \n", - "9902 1.0 0.987907 Bet.SAFE \n", - "9903 2.0 0.987907 Bet.SAFE \n", - "9904 3.0 0.703855 Bet.SAFE \n", - "9905 4.0 0.987907 Bet.SAFE \n", - "... ... ... ... \n", - "1400025 895.0 0.940166 Bet.SAFE \n", - "1400026 896.0 0.622660 Bet.RISKY \n", - "1400027 897.0 0.639917 Bet.RISKY \n", - "1400028 898.0 0.924706 Bet.SAFE \n", - "1400029 899.0 0.663706 Bet.RISKY \n", + " risk_q3 risk_max AgentID risk_level choice \n", + "2699101 0.989232 0.997649 0.0 0.964861 Bet.SAFE \n", + "2699102 0.989232 0.997649 1.0 0.962729 Bet.SAFE \n", + "2699103 0.989232 0.997649 2.0 0.926470 Bet.SAFE \n", + "2699104 0.989232 0.997649 3.0 0.989232 Bet.SAFE \n", + "2699105 0.989232 0.997649 4.0 0.932366 Bet.SAFE \n", + "... ... ... ... ... ... \n", + "13500000 0.985759 0.995927 895.0 0.912332 Bet.SAFE \n", + "13500001 0.985759 0.995927 896.0 0.909342 Bet.SAFE \n", + "13500002 0.985759 0.995927 897.0 0.986264 Bet.SAFE \n", + "13500003 0.985759 0.995927 898.0 0.986130 Bet.SAFE \n", + "13500004 0.985759 0.995927 899.0 0.891088 Bet.SAFE \n", "\n", - "[14000 rows x 15 columns]" + "[4500 rows x 15 columns]" ] }, - "execution_count": 35, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "last_step = df[df.Step == 100]\n", + "last_step = df[df.Step == last_step_n]\n", "last_step" ] }, @@ -494,7 +355,7 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 29, "id": "f9a8cbbe-13d8-4f7b-9348-15feebe7a88a", "metadata": {}, "outputs": [ @@ -504,13 +365,13 @@ "" ] }, - "execution_count": 36, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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3nC+Znz//fet9evXW/v27bMOHjxovfzyy1ZUVJS1ZcuWRp68YXzNV+/IkSPWrbfeat1///3WiBEjGmdYP/iab9WqVZbdbreOHTvmOTmdzkae2je+ZnS5XFavXr2soUOHWjt27LCOHDlilZSUWBUVFY08ecP4mu/EiRNet9/evXutqKgoa9WqVY07eAP5mm/16tWWzWazVq9ebR05csTaunWrlZKSYk2dOrWRJ284XzNOnz7dSk1NtTZt2mQdOnTIWrZsmRUbG2t99tlnjTx5w2zevNl65plnrPXr11uSrHffffeq6w8fPmzFxcVZeXl51r59+6xXX3016I8TFJgf6d27t5Wbm+s5X1tba6WmploLFy5s0NdfvHjRio+Pt956661gjXjdrjejZVlWjx49rNmzZwdjvOvmT76LFy9a9957r/Xmm29a48ePD+kC42u+VatWWS1atGik6QLD14yvvfaa1aZNG6umpqaxRrwu1/t/cMmSJVZ8fLx19uzZYI14XXzNl5ubaw0YMMDrWF5ennXfffcFdc7r4WvGlJQU6/e//73XsYcfftgaM2ZMUOcMhIYUmOnTp1t33XWX17FHHnnEysnJCdpcvIT0AzU1NSovL1dWVpbnWGRkpLKyslRWVtagPc6fPy+3261WrVoFa8zrcr0ZLctScXGxDhw4oH79+gVzVL/4m2/+/PlKTEzUhAkTGmNMv/mb7+zZs0pPT1daWppGjBihysrKxhjXL/5kfO+995SZmanc3FwlJSWpc+fOeuGFF1RbW9tYYzdYIO5nVqxYoVGjRummm24K1ph+8yffvffeq/Lycs9LMIcPH9bmzZs1dOjQRpnZV/5kdLlcl7x027x5c+3YsSOoszaWsrIyr38PScrJyWnw97Q/QvYn8TaFv//976qtrb3kp/0mJSXpyy+/bNAeM2bMUGpq6iU3ZKjwN+Pp06d16623yuVyKSoqSsuWLdOgQYOCPa7P/Mm3Y8cOrVixQhUVFY0w4fXxJ1/79u21cuVKde3aVadPn9aiRYt07733qrKyUrfddltjjO0TfzIePnxY27dv15gxY7R582YdPHhQv/rVr+R2uzV37tzGGLvBrvd+Zvfu3dq7d69WrFgRrBGviz/5Ro8erb///e/q27evLMvSxYsX9cQTT+jXv/51Y4zsM38y5uTkaPHixerXr5/atm2r4uJirV+/PiRLtj+cTudl/z2qq6v1/fffq3nz5gG/Tp6BCaAXX3xRa9eu1bvvvmvEmyR9ER8fr4qKCn3yySd6/vnnlZeXp5KSkqYe67qdOXNGY8eO1RtvvKHWrVs39ThBkZmZqXHjxql79+564IEHtH79et1yyy36wx/+0NSjBUxdXZ0SExP1+uuvq2fPnnrkkUf0zDPPaPny5U09WsCtWLFCXbp0Ue/evZt6lIApKSnRCy+8oGXLlumzzz7T+vXrtWnTJi1YsKCpRwuYV155Rf/0T/+kDh06KCYmRpMnT9Zjjz2myEgehv3FMzA/0Lp1a0VFRamqqsrreFVVlZKTk6/6tYsWLdKLL76obdu2qWvXrsEc87r4mzEyMlLt2rWTJHXv3l379+/XwoUL1b9//2CO6zNf8x06dEhfffWVhg8f7jlWV1cnSWrWrJkOHDigtm3bBndoH1zP92i96Oho9ejRQwcPHgzGiNfNn4wpKSmKjo5WVFSU51jHjh3ldDpVU1OjmJiYoM7si+u5Dc+dO6e1a9dq/vz5wRzxuviT79lnn9XYsWP1+OOPS5K6dOmic+fOadKkSXrmmWdC7kHen4y33HKLNmzYoAsXLujEiRNKTU3VzJkz1aZNm8YYOeiSk5Mv++9ht9uD8uyLxDMwXmJiYtSzZ08VFxd7jtXV1am4uFiZmZlX/LqCggItWLBAW7ZsUa9evRpjVL/5m/HH6urq5HK5gjHidfE1X4cOHfTFF1+ooqLCc/rZz36mBx98UBUVFUpLS2vM8a8pELdfbW2tvvjiC6WkpARrzOviT8b77rtPBw8e9JRPSfqf//kfpaSkhFR5ka7vNnznnXfkcrn06KOPBntMv/mT7/z585eUlPoyaoXgr+u7ntswNjZWt956qy5evKg///nPGjFiRLDHbRSZmZle/x6S5HA4fHpc8VnQ3h5sqLVr11o2m80qLCy09u3bZ02aNMlKSEjwfOx07Nix1syZMz3rX3zxRSsmJsb6z//8T6+POZ45c6apIlyTrxlfeOEFq6ioyDp06JC1b98+a9GiRVazZs2sN954o6kiXJWv+X4s1D+F5Gu+efPmWVu3brUOHTpklZeXW6NGjbJiY2OtysrKpopwTb5mPHr0qBUfH29NnjzZOnDggLVx40YrMTHReu6555oqwlX5+z3at29f65FHHmnscX3ma765c+da8fHx1p/+9Cfr8OHDVlFRkdW2bVvrX/7lX5oqwjX5mnHXrl3Wn//8Z+vQoUNWaWmpNWDAACsjI8P67rvvmijB1Z05c8bas2ePtWfPHkuStXjxYmvPnj3W//7v/1qWZVkzZ860xo4d61lf/zHqadOmWfv377eWLl3Kx6ibwquvvmrdfvvtVkxMjNW7d29r165dnsseeOABa/z48Z7z6enplqRLTnPnzm38wX3gS8ZnnnnGateunRUbG2u1bNnSyszMtNauXdsEUzecL/l+LNQLjGX5lm/KlCmetUlJSdbQoUND9mdP/JCvt+HOnTutPn36WDabzWrTpo31/PPPWxcvXmzkqRvO13xffvmlJckqKipq5En940s+t9tt5efnW23btrViY2OttLQ061e/+lXIPrjX8yVjSUmJ1bFjR8tms1k333yzNXbsWOtvf/tbE0zdMO+///5lH9vqM40fP9564IEHLvma7t27WzExMVabNm2C/nOKIiwrBJ+fAwAAuAreAwMAAIxDgQEAAMahwAAAAONQYAAAgHEoMAAAwDgUGAAAYBwKDAAAMA4FBgAAGIcCAwAAjEOBAQAAxqHAAAAA41BgAACAcf4fEgAxSkC9tQEAAAAASUVORK5CYII=", + "image/png": "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MEDr168PX6uvr9f69evl9Xpb8WQAAOBscFa+AiNJRUVFGjdunAYOHKhBgwbpiSee0NGjR3XXXXe19tEAAEArO2sD5rbbbtN///tfzZw5U36/X/369dPatWsb/WBvvLndbs2aNavR21Voecw6PphzfDDn+GDO8XE2zDnB+bbPKQEAAJxlzsqfgQEAADgdAgYAAJhDwAAAAHMIGAAAYA4B04SSkhJdfPHFSklJ0eDBg7Vt27bTrl+1apV69OihlJQU9enTR2vWrInTSe2LZtbPPfecrrvuOnXo0EEdOnRQbm7ut/5/g69F+z3dYOXKlUpISNDNN98c2wOeI6Kd86FDh1RYWKjOnTvL7Xbrsssu498fZyDaOT/xxBO6/PLL1bZtW2VnZ2vq1Kk6fvx4nE5r06ZNmzRq1ChlZWUpISFBb7755rc+pqKiQv3795fb7Vb37t1VWloa20M6iLBy5UrH5XI5L7zwgrNz507nnnvucdq3b+8EAoEm17/77rtOUlKSU1xc7OzatcuZMWOGk5yc7OzYsSPOJ7cn2lnfcccdTklJibN9+3bno48+cn7+8587aWlpzn/+8584n9yWaOfcYO/evc73vvc957rrrnNuuumm+BzWsGjnXFNT4wwcONC58cYbnc2bNzt79+51KioqnOrq6jif3JZo5/zyyy87brfbefnll529e/c669atczp37uxMnTo1zie3Zc2aNc7DDz/svP76644k54033jjt+j179jipqalOUVGRs2vXLuepp55ykpKSnLVr18bsjATMNwwaNMgpLCwM366rq3OysrKc+fPnN7n+1ltvdQoKCiKuDR482PnFL34R03OeC6Kd9TedOHHCadeunfPSSy/F6ojnhObM+cSJE87VV1/tPP/88864ceMImDMQ7ZyfeeYZ55JLLnFqa2vjdcRzQrRzLiwsdIYPHx5xraioyLnmmmties5zyZkEzLRp05wrrrgi4tptt93m5Ofnx+xcvIV0ktraWlVVVSk3Nzd8LTExUbm5uaqsrGzyMZWVlRHrJSk/P/+U6/G15sz6m44dO6ZQKKSOHTvG6pjmNXfOc+fOVXp6usaPHx+PY5rXnDm/9dZb8nq9KiwsVEZGhnr37q3HHntMdXV18Tq2Oc2Z89VXX62qqqrw20x79uzRmjVrdOONN8blzOeL1vi98Kz9k3hbw+eff666urpGf9pvRkaGPv744yYf4/f7m1zv9/tjds5zQXNm/U0PPvigsrKyGv1Dg/+vOXPevHmzli9frurq6jic8NzQnDnv2bNHGzZs0JgxY7RmzRp98sknuv/++xUKhTRr1qx4HNuc5sz5jjvu0Oeff65rr71WjuPoxIkTuvfee/WrX/0qHkc+b5zq98JgMKivvvpKbdu2bfGvySswMGnBggVauXKl3njjDaWkpLT2cc4ZR44c0dixY/Xcc8/poosuau3jnNPq6+uVnp6uZ599VgMGDNBtt92mhx9+WMuWLWvto51TKioq9Nhjj+npp5/Whx9+qNdff11lZWWaN29eax8N3xGvwJzkoosuUlJSkgKBQMT1QCCgzMzMJh+TmZkZ1Xp8rTmzbvD4449rwYIFeuedd9S3b99YHtO8aOf8z3/+U//61780atSo8LX6+npJUps2bbR7925deumlsT20Qc35fu7cubOSk5OVlJQUvtazZ0/5/X7V1tbK5XLF9MwWNWfO//d//6exY8dqwoQJkqQ+ffro6NGjmjhxoh5++GElJvLf8S3hVL8XejyemLz6IvEKTASXy6UBAwZo/fr14Wv19fVav369vF5vk4/xer0R6yXJ5/Odcj2+1pxZS1JxcbHmzZuntWvXauDAgfE4qmnRzrlHjx7asWOHqqurw79+/OMfa9iwYaqurlZ2dnY8j29Gc76fr7nmGn3yySfhQJSkv//97+rcuTPxcgrNmfOxY8caRUpDNDr8VYAtplV+L4zZjwcbtXLlSsftdjulpaXOrl27nIkTJzrt27d3/H6/4ziOM3bsWOehhx4Kr3/33XedNm3aOI8//rjz0UcfObNmzeJj1Gco2lkvWLDAcblczmuvveZ89tln4V9HjhxpradgQrRz/iY+hXRmop3zvn37nHbt2jmTJk1ydu/e7axevdpJT093HnnkkdZ6CiZEO+dZs2Y57dq1c/7whz84e/bsccrLy51LL73UufXWW1vrKZhw5MgRZ/v27c727dsdSc6iRYuc7du3O//+978dx3Gchx56yBk7dmx4fcPHqB944AHno48+ckpKSvgYdWt46qmnnK5duzoul8sZNGiQs3Xr1vB9P/jBD5xx48ZFrH/11Vedyy67zHG5XM4VV1zhlJWVxfnEdkUz627dujmSGv2aNWtW/A9uTLTf0ycjYM5ctHPesmWLM3jwYMftdjuXXHKJ8+ijjzonTpyI86ntiWbOoVDImT17tnPppZc6KSkpTnZ2tnP//fc7X3zxRfwPbsif//znJv992zDbcePGOT/4wQ8aPaZfv36Oy+VyLrnkEufFF1+M6RkTHIfX0AAAgC38DAwAADCHgAEAAOYQMAAAwBwCBgAAmEPAAAAAcwgYAABgDgEDAADMIWAAAIA5BAwAADCHgAEAAOYQMAAAwBwCBgAAmPP/AEsgwmojEp8+AAAAAElFTkSuQmCC", "text/plain": [ "
" ] @@ -521,12 +382,12 @@ ], "source": [ "# overall ending risk distribution across all runs\n", - "last_step.risk_level.hist()" + "last_step.risk_level.hist(range=[0,1])" ] }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 28, "id": "87aada54-d371-43e4-9554-6597ea6330e0", "metadata": {}, "outputs": [ @@ -536,13 +397,13 @@ "" ] }, - "execution_count": 37, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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ysoWnrxpgs33l7/8JeD+O3DggBUdHW2tXr26ZQdvomDzrV271nI6ndbatWutI0eOWFu2bLFSUlKsRx55pIUnb7pgMz766KNWamqq9dZbb1mffvqp9cILL1hxcXHWvn37Wnjyptm8ebP1+OOPWxs2bLAkWW+88cZF13/22WdWu3btrKKiIuvgwYPW0qVLbf8+QYH5nsGDB1sFBQX+y/X19VZqaqo1f/78Jl3/3LlzVocOHayXX37ZrhEv2+VmtCzLuv76663p06fbMd5la06+c+fOWTfeeKP1z//8z1Z+fn5YF5hg861evdrq2LFjC00XGsFmfPHFF62rrrrKqqura6kRL8vlfg0+++yzVocOHazTp0/bNeJlCTZfQUGBdfvttwccKyoqsm666SZb57wcwWZMSUmxnn/++YBjd999t5WXl2frnKHQlALz6KOPWn379g04du+991o5OTm2zcVLSN9RV1eniooKZWVl+Y9FRUUpKytL5eXlTdqjtrZWXq9XiYmJdo15WS43o2VZ2rp1qw4fPqyhQ4faOWqzNDff3Llz1bVrV02YMKElxmy25uY7ffq0unfvrrS0NI0dO1ZVVVUtMW6zNCfjm2++qczMTBUUFCgpKUn9+vXTU089pfr6+pYau8lC8TizcuVK5ebmqn379naN2WzNyXfjjTeqoqLC/xLMZ599ps2bN2vUqFEtMnOwmpPR4/E0eum2bdu2ev/9922dtaWUl5cH/HtIUk5OTpM/p5sjbH8Tb2v485//rPr6+ka/7TcpKUl/+tOfmrTHY489ptTU1EZ3ZLhobsaTJ0/qiiuukMfjUXR0tF544QWNGDHC7nGD1px877//vlauXKnKysoWmPDyNCdfr169tGrVKg0YMEAnT57UwoULdeONN6qqqkpXXnllS4wdlOZk/Oyzz7Rt2zbl5eVp8+bN+uSTT/TQQw/J6/Vq1qxZLTF2k13u48zu3bt14MABrVy50q4RL0tz8t13333685//rJtvvlmWZencuXP69a9/rd/+9rctMXLQmpMxJydHixYt0tChQ9WzZ09t3bpVGzZsCMuS3Rxut/u8/x41NTX6v//7P7Vt2zbkt8kzMCH09NNPa/369XrjjTeM+CHJYHTo0EGVlZXas2ePnnzySRUVFWn79u2tPdZlO3XqlMaPH6+XXnpJXbp0ae1xbJGZman7779fAwcO1K233qoNGzboxz/+sX73u9+19mgh4/P51LVrV61YsUKDBg3Svffeq8cff1zLly9v7dFCbuXKlerfv78GDx7c2qOEzPbt2/XUU0/phRde0L59+7Rhwwa99dZbmjdvXmuPFjLPPfecrrnmGl177bWKjY1VYWGhHnjgAUVF8W24uXgG5ju6dOmi6OhoVVdXBxyvrq5WcnLyRa+7cOFCPf3003r77bc1YMAAO8e8LM3NGBUVpauvvlqSNHDgQB06dEjz58/XsGHD7Bw3aMHm+/TTT/X5559rzJgx/mM+n0+S1KZNGx0+fFg9e/a0d+ggXM7naIOYmBhdf/31+uSTT+wY8bI1J2NKSopiYmIUHR3tP9a7d2+53W7V1dUpNjbW1pmDcTn34ZkzZ7R+/XrNnTvXzhEvS3PyzZgxQ+PHj9ff/d3fSZL69++vM2fOaNKkSXr88cfD7pt8czL++Mc/1saNG3X27Fn95S9/UWpqqqZOnaqrrrqqJUa2XXJy8nn/PeLj42159kXiGZgAsbGxGjRokLZu3eo/5vP5tHXrVmVmZl7wesXFxZo3b55KSkqUkZHREqM2W3Mzfp/P55PH47FjxMsSbL5rr71W+/fvV2Vlpf/jF7/4hW677TZVVlYqLS2tJce/pFDcf/X19dq/f79SUlLsGvOyNCfjTTfdpE8++cRfPiXpo48+UkpKSliVF+ny7sPXXntNHo9Hv/zlL+0es9mak6+2trZRSWkoo1YY/rm+y7kP4+LidMUVV+jcuXN6/fXXNXbsWLvHbRGZmZkB/x6S5HK5gvq+EjTbfjzYUOvXr7ecTqe1Zs0a6+DBg9akSZOshIQE/9tOx48fb02dOtW//umnn7ZiY2Otf//3fw94m+OpU6daK8IlBZvxqaeeskpLS61PP/3UOnjwoLVw4UKrTZs21ksvvdRaES4q2HzfF+7vQgo235w5c6wtW7ZYn376qVVRUWHl5uZacXFxVlVVVWtFuKRgMx49etTq0KGDVVhYaB0+fNjatGmT1bVrV+uJJ55orQgX1dzP0Ztvvtm69957W3rcoAWbb9asWVaHDh2s3//+99Znn31mlZaWWj179rTuueee1opwScFm3Llzp/X6669bn376qVVWVmbdfvvtVnp6uvXNN9+0UoKLO3XqlPXBBx9YH3zwgSXJWrRokfXBBx9YX3zxhWVZljV16lRr/Pjx/vUNb6OeMmWKdejQIWvZsmW8jbo1LF261OrWrZsVGxtrDR482Nq5c6f/3K233mrl5+f7L3fv3t2S1Ohj1qxZLT94EILJ+Pjjj1tXX321FRcXZ3Xq1MnKzMy01q9f3wpTN10w+b4v3AuMZQWXb/Lkyf61SUlJ1qhRo8L2d098V7D34Y4dO6whQ4ZYTqfTuuqqq6wnn3zSOnfuXAtP3XTB5vvTn/5kSbJKS0tbeNLmCSaf1+u1Zs+ebfXs2dOKi4uz0tLSrIceeihsv7k3CCbj9u3brd69e1tOp9Pq3LmzNX78eOurr75qhamb5p133jnv97aGTPn5+datt97a6DoDBw60YmNjrauuusr231PksKwwfH4OAADgIvgZGAAAYBwKDAAAMA4FBgAAGIcCAwAAjEOBAQAAxqHAAAAA41BgAACAcSgwAADAOBQYAABgHAoMAAAwDgUGAAAYhwIDAACM8/8AXhNOE30u5h4AAAAASUVORK5CYII=", + "image/png": "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a56/JQtMY8ydO1d5eXn+27W1tTp79qxat26tqKiokD2O1+tVWlqa/vGPf8jhcIRsXdRH1pFBzpFBzpFBzpERzpwty9KFCxeUmpp63blbssC0adNGMTExqqioCDheUVEhp9PZ4H3sdrvsdnvAscTExHBtUQ6Hg78cEULWkUHOkUHOkUHOkRGunK/3zEudW/JNvDabTX369FFJSYn/WG1trUpKSuRyuZpwZwAA4FZwSz4DI0l5eXmaOHGi+vbtq379+unFF1/UpUuX9Itf/KKptwYAAJrYLVtgHnvsMf3v//6v5s+fL4/Ho169emnz5s313tgbaXa7XQsWLKj3chVCj6wjg5wjg5wjg5wj41bIOcq60eeUAAAAbjG35HtgAAAArocCAwAAjEOBAQAAxqHAAAAA41BgGlBYWKh77rlHcXFx6t+/v/bt23fd+bfeekudO3dWXFycunfvrk2bNkVop+YLJus//elPeuihh9SyZUu1bNlSmZmZN/zfBl8L9nu6zpo1axQVFaVHHnkkvBu8TQSb87lz55Sbm6uUlBTZ7Xbdd999/PfjJgSb84svvqhOnTqpefPmSktL08yZM3X16tUI7dZMO3fu1KhRo5SamqqoqCi98847N7zP9u3bdf/998tut6tjx44qKioK7yYtBFizZo1ls9ms119/3Tp48KA1efJkKzEx0aqoqGhwfvfu3VZMTIxVUFBgHTp0yJo3b54VGxtrHThwIMI7N0+wWY8dO9YqLCy09u/fbx0+fNj6+c9/biUkJFiff/55hHdulmBzrnPixAnrn/7pn6yHHnrIevjhhyOzWYMFm3NlZaXVt29fa+TIkdauXbusEydOWNu3b7fKy8sjvHOzBJvzqlWrLLvdbq1atco6ceKEtWXLFislJcWaOXNmhHdulk2bNllPPfWU9fbbb1uSrPXr1193/vjx41Z8fLyVl5dnHTp0yHr55ZetmJgYa/PmzWHbIwXmO/r162fl5ub6b9fU1FipqalWfn5+g/M/+clPrJycnIBj/fv3t/7lX/4lrPu8HQSb9XdVV1dbLVq0sN58881wbfG20Jicq6urrR/84AfWa6+9Zk2cOJECcxOCzXn58uXWvffea1VVVUVqi7eFYHPOzc21hgwZEnAsLy/PeuCBB8K6z9vJzRSY2bNnW127dg049thjj1nZ2dlh2xcvIX1LVVWVysrKlJmZ6T8WHR2tzMxMlZaWNnif0tLSgHlJys7OvuY8vtaYrL/r8uXL8vl8atWqVbi2abzG5rx48WIlJSVp0qRJkdim8RqT87vvviuXy6Xc3FwlJyerW7duevbZZ1VTUxOpbRunMTn/4Ac/UFlZmf9lpuPHj2vTpk0aOXJkRPZ8p2iKfwtv2Z/E2xS++OIL1dTU1Ptpv8nJyfr0008bvI/H42lw3uPxhG2ft4PGZP1dc+bMUWpqar2/NPhGY3LetWuXVq5cqfLy8gjs8PbQmJyPHz+ubdu2ady4cdq0aZOOHj2qX/3qV/L5fFqwYEEktm2cxuQ8duxYffHFF3rwwQdlWZaqq6s1depU/eY3v4nElu8Y1/q30Ov16sqVK2revHnIH5NnYGCk5557TmvWrNH69esVFxfX1Nu5bVy4cEHjx4/Xn/70J7Vp06apt3Nbq62tVVJSkl599VX16dNHjz32mJ566imtWLGiqbd2W9m+fbueffZZvfLKK/rb3/6mt99+Wxs3btSSJUuaemv4P+IZmG9p06aNYmJiVFFREXC8oqJCTqezwfs4nc6g5vG1xmRd5/nnn9dzzz2nrVu3qkePHuHcpvGCzfnYsWP67LPPNGrUKP+x2tpaSVKzZs105MgRdejQIbybNlBjvp9TUlIUGxurmJgY/7EuXbrI4/GoqqpKNpstrHs2UWNy/u1vf6vx48fr8ccflyR1795dly5d0pQpU/TUU08pOpr/Hx8K1/q30OFwhOXZF4lnYALYbDb16dNHJSUl/mO1tbUqKSmRy+Vq8D4ulytgXpLcbvc15/G1xmQtSQUFBVqyZIk2b96svn37RmKrRgs2586dO+vAgQMqLy/3f/3oRz/S4MGDVV5errS0tEhu3xiN+X5+4IEHdPToUX9BlKT//u//VkpKCuXlGhqT8+XLl+uVlLrSaPGrAEOmSf4tDNvbgw21Zs0ay263W0VFRdahQ4esKVOmWImJiZbH47Esy7LGjx9vPfnkk/753bt3W82aNbOef/556/Dhw9aCBQv4GPVNCjbr5557zrLZbNZf//pX6/Tp0/6vCxcuNNUlGCHYnL+LTyHdnGBzPnnypNWiRQtr+vTp1pEjR6wNGzZYSUlJ1tNPP91Ul2CEYHNesGCB1aJFC+svf/mLdfz4cau4uNjq0KGD9ZOf/KSpLsEIFy5csPbv32/t37/fkmS98MIL1v79+63/+Z//sSzLsp588klr/Pjx/vm6j1HPmjXLOnz4sFVYWMjHqJvCyy+/bLVr186y2WxWv379rL179/rP/fCHP7QmTpwYML9u3Trrvvvus2w2m9W1a1dr48aNEd6xuYLJun379pakel8LFiyI/MYNE+z39LdRYG5esDnv2bPH6t+/v2W32617773XeuaZZ6zq6uoI79o8weTs8/mshQsXWh06dLDi4uKstLQ061e/+pX11VdfRX7jBvnP//zPBv97W5ftxIkTrR/+8If17tOrVy/LZrNZ9957r/XGG2+EdY9RlsVzaAAAwCy8BwYAABiHAgMAAIxDgQEAAMahwAAAAONQYAAAgHEoMAAAwDgUGAAAYBwKDAAAMA4FBgAAGIcCAwAAjEOBAQAAxqHAAAAA4/w/XqegE8EwE8sAAAAASUVORK5CYII=", "text/plain": [ "
" ] @@ -553,7 +414,7 @@ ], "source": [ "# does it look any different if we change the number of bins?\n", - "last_step.risk_level.hist(bins=20)" + "last_step.risk_level.hist(bins=20, range=[0, 1])" ] }, { @@ -566,18 +427,17 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": 25, "id": "bc8c0f48-d4bb-4197-a57b-c924bd41c649", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,\n", - " 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29])" + "array([0, 1, 2, 3, 4])" ] }, - "execution_count": 38, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } @@ -588,17 +448,17 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 26, "id": "cef78a71-4c96-4628-925b-4cc63c57d5f5", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "30" + "5" ] }, - "execution_count": 39, + "execution_count": 26, "metadata": {}, "output_type": "execute_result" } @@ -609,15 +469,95 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 45, + "id": "64d59cd5-7058-4940-9187-f30b6c50a1cc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "count 4500.000000\n", + "mean 0.959786\n", + "std 0.030035\n", + "min 0.868069\n", + "25% 0.933203\n", + "50% 0.963950\n", + "75% 0.986489\n", + "max 0.998929\n", + "Name: risk_level, dtype: float64" + ] + }, + "execution_count": 45, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "last_step.risk_level.describe()" + ] + }, + { + "cell_type": "code", + "execution_count": 44, "id": "358468a7-f223-4410-b4bd-f614a2b339fa", "metadata": {}, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "count 900.000000\n", + "mean 0.959834\n", + "std 0.027286\n", + "min 0.921671\n", + "25% 0.933203\n", + "50% 0.961884\n", + "75% 0.989232\n", + "max 0.997649\n", + "Name: risk_level, dtype: float64\n", + "count 900.000000\n", + "mean 0.970092\n", + "std 0.021066\n", + "min 0.882463\n", + "25% 0.956693\n", + "50% 0.975349\n", + "75% 0.975349\n", + "max 0.997409\n", + "Name: risk_level, dtype: float64\n", + "count 900.000000\n", + "mean 0.965771\n", + "std 0.012998\n", + "min 0.896907\n", + "25% 0.962232\n", + "50% 0.963950\n", + "75% 0.963950\n", + "max 0.997160\n", + "Name: risk_level, dtype: float64\n", + "count 900.000000\n", + "mean 0.964561\n", + "std 0.032457\n", + "min 0.868069\n", + "25% 0.927329\n", + "50% 0.986650\n", + "75% 0.990270\n", + "max 0.998929\n", + "Name: risk_level, dtype: float64\n", + "count 900.000000\n", + "mean 0.938672\n", + "std 0.038634\n", + "min 0.891088\n", + "25% 0.906854\n", + "50% 0.912332\n", + "75% 0.985759\n", + "max 0.995927\n", + "Name: risk_level, dtype: float64\n" + ] + }, { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABbEAAAQrCAYAAACyzOGxAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzdeXxU5d3///dkYUgCSQjIEg2goIKsFhoJlUWIhEWoG8stN4IPCkqB3mpbRW9lEaWKCtRAQS2KaGMFRVRALSJUxYCI1S9SZRMQZRO4SSCBEJLr94e/TJnMZJlkljNnXs/HYx6aM1fOdV1nznuu4ZOZMw5jjBEAAAAAAAAAABYUFeoBAAAAAAAAAABQEYrYAAAAAAAAAADLoogNAAAAAAAAALAsitgAAAAAAAAAAMuiiA0AAAAAAAAAsCyK2AAAAAAAAAAAy6KIDQAAAAAAAACwLIrYAAAAAAAAAADLoogNAAAAAAAAALAsitjwqzFjxqhly5ZVttu3b58cDoeWLFkS8DF507JlS40ZMyYkfQPBFC6ZBFAz4ZJx1l1EinDJJICaCZeMs+4iUoRLJuEfFLGr4fTp05o2bZr69++vlJSUKk/8b775Rv3791e9evWUkpKiUaNG6aeffgq7vlE7OTk5mjdvXqiHYUtkErC3LVu2aNKkSWrXrp0SEhLUvHlzDRs2TDt37vTa3p85C2XfqB3W3cAhk4C9bd++XUOHDtVll12m+Ph4NWrUSD179tQ777zjtb0/cxbKvlE7rLuBQyYB72JCPYBwcOzYMT3yyCNq3ry5OnXqpA0bNlTY9ocfflDPnj2VlJSkWbNm6fTp03rqqae0bds2ffbZZ6pTp07Y9F0Tzz//vEpLSwPeTzjIycnR119/rbvvvjvUQ7EdMll9ZBLh6IknntDGjRs1dOhQdezYUYcPH9b8+fP1i1/8Qps2bVL79u1dbf2ds1D2XRNk/D9YdwOHTFYfmUQ42r9/v06dOqXRo0crNTVVhYWFeuONNzRkyBA9++yzGj9+vKutv3MWyr5rgoz/B+tu4JDJ6iOTEcagSmfPnjWHDh0yxhizZcsWI8m8+OKLXttOmDDBxMXFmf3797u2rV271kgyzz77bFj17YvTp0/71H7v3r2VziXQWrRoYUaPHh3QPgYNGmRatGgR0D4iFZmsWrhl0p8KCgpCPQTU0saNG01RUZHbtp07dxqn02lGjhzptt3fOQtl374It4yz7oY3Mlm1cMukP7Hu2tP58+dNp06dzJVXXum2PRg5C2XfFQm3jLPu2g+ZdBdumfSnSF53uZxINTidTjVt2rRabd944w3dcMMNat68uWtbZmamrrjiCi1btkySZIzRddddp4suukhHjx51tTt37pw6dOigVq1aqaCgICB9V+b48eMaNWqUEhMTlZycrNGjR+urr77yuFzCmDFjVK9ePe3Zs0cDBw5U/fr1NXLkSNd95a9HdPLkSY0ZM0ZJSUmu/Z48ebJac6rIiy++qD59+qhx48ZyOp266qqrtHDhQo92xhg9+uijuuSSSxQfH6/rrrtO27dv97rP7777TkOHDlVKSori4+PVrVs3rV692q3Nhg0b5HA49Nprr+nBBx9U06ZNlZCQoCFDhujAgQOudr1799bq1au1f/9+ORwOORyOal2nCdVDJsMzkzfccIMuu+wyr7+fkZGhrl27um175ZVX1KVLF8XFxSklJUUjRoxwy5n0c9bat2+vrVu3qmfPnoqPj9eDDz4oSXrrrbc0aNAgpaamyul0qlWrVpo5c6ZKSko8+l+wYIEuu+wyxcXFKT09XR9//LF69+6t3r17u7UrKirStGnT1Lp1azmdTqWlpem+++5TUVGRr4cMlejevbvHOzcuv/xytWvXTt98843bdn9n3N99V8ZuGZdYd+2KTIZnJll3URvR0dFKS0vzOFf9nXF/910Zu2VcYt2NJGTS+plk3Q08LifiRz/++KOOHj3qcWJKUnp6utasWSNJcjgceuGFF9SxY0fdddddWrFihSRp2rRp2r59uzZs2KCEhISA9F2R0tJSDR48WJ999pkmTJigNm3a6K233tLo0aO9tj9//ryysrJ07bXX6qmnnlJ8fLzXdsYY/frXv9Ynn3yiu+66S23bttWbb75Z4X6ra+HChWrXrp2GDBmimJgYvfPOO/rtb3+r0tJSTZw40dVu6tSpevTRRzVw4EANHDhQX3zxhfr166dz58657e/IkSPq3r27CgsL9bvf/U4NGzbUSy+9pCFDhuj111/XTTfd5Nb+sccek8Ph0P3336+jR49q3rx5yszM1Jdffqm4uDj97//+r/Ly8vTDDz9o7ty5kqR69erVas7wHZn0FMpMDh8+XLfffru2bNmiX/7yl67f3b9/vzZt2qQnn3zSte2xxx7Tww8/rGHDhuk3v/mNfvrpJ2VnZ6tnz57617/+peTkZFfb48ePa8CAARoxYoT++7//W02aNJEkLVmyRPXq1dO9996revXq6cMPP9TUqVOVn5/v1tfChQs1adIk9ejRQ/fcc4/27dunG2+8UQ0aNNAll1zialdaWqohQ4bok08+0fjx49W2bVtt27ZNc+fO1c6dO7Vy5cpaHUNUzhijI0eOqF27dq5twcp4bfquiB0zLrHuRhIyaf1Msu7CVwUFBTpz5ozy8vL09ttv691339Xw4cNd9wcy4/7quyJ2zLjEumt3ZPI/wiGTrLtBEOR3foe9yi4fUHbf0qVLPe774x//aCSZs2fPurY9++yzRpJ55ZVXzKZNm0x0dLS5++67g9J3eW+88YaRZObNm+faVlJSYvr06ePR5+jRo40kM2XKFI/9jB492u0jRStXrjSSzOzZs13bzp8/b3r06FGrj3IUFhZ6bMvKyjKXXXaZ6+ejR4+aOnXqmEGDBpnS0lLX9gcffNBIcvt41d13320kmY8//ti17dSpU+bSSy81LVu2NCUlJcYYY9avX28kmYsvvtjk5+e72i5btsxIMn/+859d2/h4VXCQyfDJZF5ennE6neb3v/+9W7vZs2cbh8Ph+hjavn37THR0tHnsscfc2m3bts3ExMS4be/Vq5eRZBYtWlStMd15550mPj7edeyLiopMw4YNzS9/+UtTXFzsardkyRIjyfTq1cu17eWXXzZRUVFuzxPGGLNo0SIjyWzcuNGjP/jPyy+/bCSZxYsXu7YFOuP+7Ls8O2acdTeykEnrZ5J1F7668847jSQjyURFRZlbb73VnDhxwnV/IDPu777Ls2PGWXftj0z+LFwyybobeFxOxI/OnDkj6efLDZRXt25dtzaSNH78eGVlZWny5MkaNWqUWrVqpVmzZgWl7/Lee+89xcbGaty4ca5tUVFRbn/lLW/ChAlVjmvNmjWKiYlxaxsdHa3JkydX+buViYuLc/1/Xl6ejh07pl69eum7775TXl6eJOmDDz7QuXPnNHnyZDkcDld7b188sWbNGqWnp+vaa691batXr57Gjx+vffv26d///rdb+9tvv13169d3/XzrrbeqWbNmVf61EcFFJj2FMpOJiYkaMGCAli1bJmOMq/1rr72mbt26uT6GtmLFCpWWlmrYsGE6duyY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G8iM+7vv8uyYcdZd+yOT7qyeSdbdwONyIn5UdgJ5u1bM2bNn3dqUWbx4sVq1aqVdu3bp008/9bg/kH1faP/+/WrWrJnHRzJat27ttX1MTIzbxw6q2m/5jxZdeeWVVf5uZTZu3Khp06YpNzdXhYWFbvfl5eUpKSlJ+/fvl/TzdRMvdNFFF3mEdf/+/brmmms8+mnbtq3r/vbt27u2l9+nw+FQ69attW/fvhrPCf5HJivebygyKf38EauVK1cqNzdX3bt31549e7R161bNmzfP1X7Xrl0yxnjkrExsbKzbzxdffLHXb77evn27HnroIX344YfKz8/3GJMk1/NE+eMaExPjcW21Xbt26ZtvvtFFF13kdVwXXl8O/nP48GENGjRISUlJev311xUdHe26L9AZ93ffF7Jjxll3IwOZ/Fk4ZFJi3YVv2rRpozZt2kj6uYjZr18/DR48WJs3b5bD4QhoxgPR94XsmHHWXfsjk/8RDpmUWHcDjSK2HzVr1kySdOjQIY/7Dh06pJSUFI+/Um3YsMEV+m3btikjIyNofdeG0+lUVFRo3si/Z88e9e3bV23atNGcOXOUlpamOnXqaM2aNZo7d65KS0tDMi5YD5kMDl8yOXjwYMXHx2vZsmXq3r27li1bpqioKA0dOtTVprS0VA6HQ++++65bgaJM+Rck3l4cnTx5Ur169VJiYqIeeeQRtWrVSnXr1tUXX3yh+++/v0bPE6WlperQoYPmzJnj9f60tDSf94nK5eXlacCAATp58qQ+/vhjpaamut0fyIwHou/aCJeMw97I5H+ESyZZd1Ebt956q+68807t3LlTV155ZVBfW/uj79oIl4wjspBJ62eSdTewKGL70cUXX6yLLrpIn3/+ucd9n332mTp37uy27dChQ5o8ebL69eunOnXq6A9/+IOysrLUokWLgPddXosWLbR+/XoVFha6/SVs9+7dPo+l/H7XrVun06dPuwVxx44dNd7nO++8o6KiIr399ttu34Jb/qMNZcdx165dbt8Q+9NPP+n//u//PNp6G1PZRzDKPya7du1y+9kYo927d6tjx46ubRd+pAuhQSa97zdUmZSkhIQE3XDDDVq+fLnmzJmj1157TT169HArRrRq1UrGGF166aW64oorajSmDRs26Pjx41qxYoV69uzp2r537163dmWP7e7du3Xddde5tp8/f1779u1zy3SrVq301VdfqW/fvuQ7CM6ePavBgwdr586d+uCDD3TVVVd5tAlUxgPRd3l2zDjrrr2RyZph3WXdDWdllwIoe0dfMF9b17bv8uyYcdbdyEMmq8a6a+91l2ti+9ktt9yiVatW6cCBA65t69at086dO93+8iJJ48aNU2lpqRYvXqznnntOMTExGjt2rNu1cwLVd3lZWVkqLi7W888/79pWWlqqBQsW1GgsZQYOHKjz589r4cKFrm0lJSXKzs6u8T7L/lJ14XHKy8vTiy++6NYuMzNTsbGxys7Odmt74cc4LhznZ599ptzcXNe2goICPffcc2rZsqXHP5aWLl2qU6dOuX5+/fXXdejQIQ0YMMC1LSEhwfUEj9Ahk+5Cmckyw4cP18GDB/XXv/5VX331ldu3XEvSzTffrOjoaM2YMcPj2BtjdPz48RqN6dy5c/rLX/7i1q5r165q2LChnn/+eZ0/f961/W9/+5vHi/9hw4bpxx9/dHtMypw5c0YFBQVVjgvVU1JSouHDhys3N1fLly+v9B0i/s54oPouz44ZZ921LzJZc6y7rLvhwNtHxIuLi7V06VLFxcW5PSf7O+OB6rs8O2acdde+yGTNse7afN0N7PdG2kd2draZOXOmmTBhgpFkbr75ZjNz5kwzc+ZMc/LkSVe777//3jRs2NC0atXKPPPMM2bWrFmmQYMGpkOHDm7fzPrCCy8YSWbJkiWuba+88oqRZBYsWBDQvr05f/68SU9PN9HR0WbSpElm/vz5pl+/fqZz584e4xw9erRJSEjwup/y3wxbUlJifvWrX5moqCjz29/+1syfP9/06dPHdOzY0eObYffu3evxLcrefPvtt6ZOnTqmQ4cOZv78+ebxxx83rVq1Mp06dTKSzN69e11tH3jgASPJDBw40MyfP9+MHTvWpKammkaNGrn1c/jwYdOkSROTlJRkHn74YTN37lzTuXNn43A4zIoVK1ztyr6tuUOHDqZjx45m7ty5ZsqUKaZu3bqmdevWpqCgwNV29uzZRpK55557TE5Ojnn77bcrnRd8QybDM5PGGHPmzBlTv359U79+fRMdHW2OHDnisc8//elPRpLp3r27mT17tlm4cKG57777zOWXX26efPJJV7tevXqZdu3aefz+sWPHTIMGDUyLFi3M008/bebMmWOuvvpq15jWr1/vapudnW0kmR49epjs7Gzz+9//3vW49e7d2+3YDRw40DgcDjNixAiTnZ1t5s2bZ+666y6TkpJitmzZUulxQvX9z//8j5FkBg8ebF5++WWP24X8nfFA9O2NXTPOumtPZDJ8M2kM6y6qduONN5o+ffqY6dOnm+eff97MnDnTtGnTxkgyTz/9tFtbf2c8EH17Y9eMs+7aE5kM30waw7obSBSxq6lFixZGktdb+RP266+/Nv369TPx8fEmOTnZjBw50hw+fNh1/4EDB0xSUpIZPHiwRz833XSTSUhIMN99911A+q7MTz/9ZG677TZTv359k5SUZMaMGWM2btxoJJm///3vrna+PIEYY8zx48fNqFGjTGJioklKSjKjRo0y//rXvzyeQLZt22YkmSlTplQ51rffftt07NjR1K1b17Rs2dI88cQTriflC49JSUmJmTFjhmnWrJmJi4szvXv3Nl9//bVp0aKFxxPVnj17zK233mqSk5NN3bp1TXp6ulm1apVbm7JF/dVXXzUPPPCAady4sYmLizODBg0y+/fvd2t7+vRpc9ttt5nk5GQjyeO4oHbIZHhmsszIkSONJJOZmVnhPt944w1z7bXXmoSEBJOQkGDatGljJk6caHbs2OFqU9GibowxGzduNN26dTNxcXEmNTXV3Hfffeb999/3WNSNMeaZZ54xLVq0ME6n06Snp5uNGzeaLl26mP79+7u1O3funHniiSdMu3btjNPpNA0aNDBdunQxM2bMMHl5eVUeJ1RPr169KsyYt7+/+zPj/u67MnbMOOuuPZHJ8M1kGdZdVObVV181mZmZpkmTJiYmJsY0aNDAZGZmmrfeestre39m3N99V8aOGWfdtScyGb6ZLMO6GxgUsVGpN99800gyn3zyScD7WrBggUlISKj2E14olC3qy5cvD/VQEKHIZHCUlJSYlJQU85vf/CbUQ0GEIePuWHcRamQyOFh3ESpk3B3rLkKNTAZHuK67XBMbLmUX6i9Tdt2gxMRE/eIXvwh4/+vXr9fvfvc7NWnSJOB9AeGATAbH2bNnPa5FtnTpUp04cUK9e/cOzaAQEcg4YC1kMjhYdxEqZBywFjIZHHZad2NCPQBYx+TJk3XmzBllZGSoqKhIK1as0KeffqpZs2YpLi4u4P0vX7484H0A4YRMBsemTZt0zz33aOjQoWrYsKG++OILLV68WO3bt6/yC0mA2iDj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Ll26KC4uTikpKRoxYoRbzqSfs9a+fXtt3bpVPXv2VHx8vB588EFJ0ltvvaVBgwYpNTVVTqdTrVq10syZM1VSUuLR/4IFC3TZZZcpLi5O6enp+vjjj9W7d2/17t3brV1RUZGmTZum1q1by+l0Ki0tTffdd5+Kiop8PWSoRPfu3T3euXH55ZerXbt2+uabb9y2+zvj/u67MnbLuMS6a1dkMjwzybqL2oiOjlZaWprHuervjPu778rYLeMS624kIZPWzyTrbuBxORE/+vHHH3X06FGPE1OS0tPTtWbNGkmSw+HQCy+8oI4dO+quu+7SihUrJEnTpk3T9u3btWHDBiUkJASk74qUlpZq8ODB+uyzzzRhwgS1adNGb731lkaPHu21/fnz55WVlaVrr71WTz31lOLj4722M8bo17/+tT755BPdddddatu2rd58880K91tdCxcuVLt27TRkyBDFxMTonXfe0W9/+1uVlpZq4sSJrnZTp07Vo48+qoEDB2rgwIH64osv1K9fP507d85tf0eOHFH37t1VWFio3/3ud2rYsKFeeuklDRkyRK+//rpuuukmt/aPPfaYHA6H7r//fh09elTz5s1TZmamvvzyS8XFxel///d/lZeXpx9++EFz586VJNWrV69Wc4bvyKSnUGZy+PDhuv3227Vlyxb98pe/dP3u/v37tWnTJj355JOubY899pgefvhhDRs2TL/5zW/0008/KTs7Wz179tS//vUvJScnu9oeP35cAwYM0IgRI/Tf//3fatKkiSRpyZIlqlevnu69917Vq1dPH374oaZOnar8/Hy3vhYuXKhJkyapR48euueee7Rv3z7deOONatCggS655BJXu9LSUg0ZMkSffPKJxo8fr7Zt22rbtm2aO3eudu7cqZUrV9bqGKJyxhgdOXJE7dq1c20LVsZr03dF7JhxiXU3kpBJ62eSdRe+Kigo0JkzZ5SXl6e3335b7777roYPH+66P5AZ91ffFbFjxiXWXbsjk/8RDplk3Q2CIL/zO+xVdvmAsvuWLl3qcd8f//hHI8mcPXvWte3ZZ581kswrr7xiNm3aZKKjo83dd98dlL7Le+ONN4wkM2/ePNe2kpIS06dPH48+R48ebSSZKVOmeOxn9OjRbh8pWrlypZFkZs+e7dp2/vx506NHj1p9lKOwsNBjW1ZWlrnssstcPx89etTUqVPHDBo0yJSWlrq2P/jgg0aS28er7r77biPJfPzxx65tp06dMpdeeqlp2bKlKSkpMcYYs379eiPJXHzxxSY/P9/VdtmyZUaS+fOf/+zaxsergoNMhk8m8/LyjNPpNL///e/d2s2ePds4HA7Xx9D27dtnoqOjzWOPPebWbtu2bSYmJsZte69evYwks2jRomqN6c477zTx8fGuY19UVGQaNmxofvnLX5ri4mJXuyVLlhhJplevXq5tL7/8somKinJ7njDGmEWLFhlJZuPGjR79wX9efvllI8ksXrzYtS3QGfdn3+XZMeOsu5GFTFo/k6y78NWdd95pJBlJJioqytx6663mxIkTrvsDmXF/912eHTPOumt/ZPJn4ZJJ1t3A43IifnTmzBlJP19uoLy6deu6tZGk8ePHKysrS5MnT9aoUaPUqlUrzZo1Kyh9l/fee+8pNjZW48aNc22Liopy+ytveRMmTKhyXGvWrFFMTIxb2+joaE2ePLnK361MXFyc6//z8vJ07Ngx9erVS999953y8vIkSR988IHOnTunyZMny+FwuNp7++KJNWvWKD09Xddee61rW7169TR+/Hjt27dP//73v93a33777apfv77r51tvvVXNmjWr8q+NCC4y6SmUmUxMTNSAAQO0bNkyGWNc7V977TV169bN9TG0FStWqLS0VMOGDdOxY8dct6ZNm+ryyy/X+vXr3fp2Op264447Kh3TqVOndOzYMfXo0UOFhYX69ttvJUmff/65jh8/rnHjxikm5j8fTho5cqQaNGjgtr/ly5erbdu2atOmjdu4+vTpI0ke44L/fPvtt5o4caIyMjLc3kURjIz7q+/y7Jhx1t3IQSZ/ZvVMsu7CV3fffbfWrl2rl156SQMGDFBJSYnbO3oDmXF/912eHTPOumt/ZNKd1TPJuht4XE7Ej8pOIG/Xijl79qxbmzKLFy9Wq1attGvXLn366ace9wey7wvt379fzZo18/hIRuvWrb22j4mJcfvYQVX7Lf/RoiuvvLLK363Mxo0bNW3aNOXm5qqwsNDtvry8PCUlJWn//v2Sfr5u4oUuuugij7Du379f11xzjUc/bdu2dd3fvn171/by+3Q4HGrdurX27dtX4znB/8hkxfsNRSalnz9itXLlSuXm5qp79+7as2ePtm7dqnnz5rna79q1S8YYj5yViY2Ndfv54osv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3f7YLeMS466TkEnrZ5JxN/y4nUgIffPNN/r2228rHJiSlJWVpTVr1kiSXC6XHn/8cXXq1Em33XabXnjhBUnSjBkztGPHDm3YsEF16tQJy7orU1JSokGDBun999/XuHHj1LZtW7388ssaOXKkz/Znz55V37591b17dz344INKTk722c4Yo1/84hd67733dNttt6ldu3Z68cUXK33dQD366KNq3769Bg8erISEBL366qv67W9/q5KSEo0fP97Tbvr06br33ns1YMAADRgwQB999JH69OmjM2fOeL3e4cOHdfnll+vUqVP63e9+p4YNG+qJJ57Q4MGD9c9//lPXXXedV/v77rtPLpdLd955p7799lstWrRIOTk5+uSTT5SUlKQ//OEPys/P19dff62FCxdKkurWrVujmhE8MllRNDN5880365ZbbtEHH3ygSy+91PO7Bw4c0JYtW/SnP/3Js+y+++7TPffco5tuukm//vWv9d1332nx4sW66qqr9PHHH6t+/fqetnl5eerfv7+GDh2qX/3qV2ratKkkacWKFapbt66mTJmiunXr6s0339T06dNVUFDgta5HH31UEyZM0JVXXqnJkydr//79uvbaa9WgQQOde+65nnYlJSUaPHiw3nvvPY0dO1bt2rXTp59+qoULF+qLL77QSy+9VKNtCP+MMTp8+LDat2/vWRapjNdk3ZWxY8Ylxl0nIZPWzyTjLoJ18uRJ/fDDD8rPz9crr7yif/3rX7r55ps9z4cz46Fad2XsmHGJcdfuyORPYiGTjLsREOErv2Oev9sHlD735JNPVnjujjvuMJLM6dOnPcv++te/GknmH//4h9myZYuJj483kyZNisi6y3v++eeNJLNo0SLPsuLiYtOrV68K6xw5cqSRZKZNm1bhdUaOHOn1kaKXXnrJSDLz58/3LDt79qy58sora/RRjlOnTlVY1rdvX9O6dWvPz99++62pVauWGThwoCkpKfEsv/vuu40kr49XTZo0yUgy7777rmfZ8ePHTatWrUzLli1NcXGxMcaYt956y0gy55xzjikoKPC0fe6554wk85e//MWzjI9XRQaZjJ1M5ufnG7fbbW6//XavdvPnzzcul8vzMbT9+/eb+Ph4c99993m1+/TTT01CQoLX8h49ehhJZunSpQH16Te/+Y1JTk72bPvCwkLTsGFDc+mll5qioiJPuxUrVhhJpkePHp5lf//7301cXJzXecIYY5YuXWokmY0bN1ZYH0Ln73//u5Fkli1b5lkW7oyHct3l2THjjLvOQiatn0nGXQTrN7/5jZFkJJm4uDhzww03mKNHj3qeD2fGQ73u8uyYccZd+yOTP4qVTDLuhh+3EwmhH374QdKPtxsor3bt2l5tJGns2LHq27evJk6cqBEjRqhNmzaaO3duRNZd3tq1a5WYmKgxY8Z4lsXFxXn9lbe8cePGVdmvNWvWKCEhwattfHy8Jk6cWOXv+pOUlOT5//z8fB05ckQ9evTQ3r17lZ+fL0l64403dObMGU2cOFEul8vT3tcXT6xZs0ZZWVnq3r27Z1ndunU1duxY7d+/X//73/+82t9yyy2qV6+e5+cbbrhBzZs3r/KvjYgsMllRNDOZkpKi/v3767nnnpMxxtP+2Wef1WWXXeb5GNoLL7ygkpIS3XTTTTpy5Ijn0axZM51//vl66623vNbtdrt16623+u3T8ePHdeTIEV155ZU6deqUPv/8c0nShx9+qLy8PI0ZM0YJCT99OGn48OFq0KCB1+utWrVK7dq1U9u2bb361atXL0mq0C+Ezueff67x48crOzvb6yqKSGQ8VOsuz44ZZ9x1DjL5I6tnknEXwZo0aZLWrVunJ554Qv3791dxcbHXFb3hzHio112eHTPOuGt/ZNKb1TPJuBt+3E4khEoPIF/3ijl9+rRXm1LLli1TmzZttGvXLm3atKnC8+Fcd1kHDhxQ8+bNK3wk47zzzvPZPiEhwetjB1W9bvmPFl144YVV/q4/Gzdu1IwZM7R582adOnXK67n8/HylpqbqwIEDkn68b2JZjRs3rhDWAwcOqFu3bhXW065dO8/zHTp08Cwv/5oul0vnnXee9u/fX+2aEHpksvLXjUYmpR8/YvXSSy9p8+bNuvzyy7Vnzx5t27ZNixYt8rTftWuXjDEVclYqMTHR6+dzzjnH5zdf79ixQ3/84x/15ptvqqCgoEKfJHnOE+W3a0JCQoV7q+3atUufffaZGjdu7LNfZe8vh9DJzc3VwIEDlZqaqn/+85+Kj4/3PBfujId63WXZMeOMu85AJn8UC5mUGHcRnLZt26pt27aSfpzE7NOnjwYNGqStW7fK5XKFNePhWHdZdsw44679kcmfxEImJcbdcGMSO4SaN28uSTp06FCF5w4dOqS0tLQKf6XasGGDJ/SffvqpsrOzI7bumnC73YqLi86F/Hv27FHv3r3Vtm1bLViwQBkZGapVq5bWrFmjhQsXqqSkJCr9gvWQycgIJpODBg1ScnKynnvuOV1++eV67rnnFBcXpxtvvNHTpqSkRC6XS//617+8JihKlX9D4uvN0bFjx9SjRw+lpKRo9uzZatOmjWrXrq2PPvpId955Z7XOEyUlJerYsaMWLFjg8/mMjIygXxP+5efnq3///jp27Jjeffddpaenez0fzoyHY901ESsZh72RyZ/ESiYZd1ETN9xwg37zm9/oiy++0IUXXhjR99ahWHdNxErG4Sxk0vqZZNwNLyaxQ+icc85R48aN9eGHH1Z47v3331fnzp29lh06dEgTJ05Unz59VKtWLf3+979X3759lZmZGfZ1l5eZmam33npLp06d8vpL2O7du4PuS/nXXb9+vU6cOOEVxJ07d1b7NV999VUVFhbqlVde8foW3PIfbSjdjrt27fL6htjvvvtO33//fYW2vvpU+hGM8vtk165dXj8bY7R792516tTJs6zsR7oQHWTS9+tGK5OSVKdOHf385z/XqlWrtGDBAj377LO68sorvSYj2rRpI2OMWrVqpQsuuKBafdqwYYPy8vL0wgsv6KqrrvIs37dvn1e70n27e/duXX311Z7lZ8+e1f79+70y3aZNG/3nP/9R7969yXcEnD59WoMGDdIXX3yhN954QxdddFGFNuHKeDjWXZ4dM864a29ksnoYdxl3Y1nprQBKr+iL5Hvrmq67PDtmnHHXechk1Rh37T3uck/sEBsyZIhee+01ffXVV55l69ev1xdffOH1lxdJGjNmjEpKSrRs2TL97W9/U0JCgkaPHu1175xwrbu8vn37qqioSI899phnWUlJiZYsWVKtvpQaMGCAzp49q0cffdSzrLi4WIsXL672a5b+parsdsrPz9fy5cu92uXk5CgxMVGLFy/2alv2Yxxl+/n+++9r8+bNnmUnT57U3/72N7Vs2bLCP5aefPJJHT9+3PPzP//5Tx06dEj9+/f3LKtTp47nBI/oIZPeopnJUjfffLMOHjyo//u//9N//vMfr2+5lqTrr79e8fHxmjVrVoVtb4xRXl5etfp05swZPfLII17tunbtqoYNG+qxxx7T2bNnPcufeuqpCm/+b7rpJn3zzTde+6TUDz/8oJMn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iJVMIjSYxA7AiRMnNGPGDPXr109paWlVHvifffaZ+vXrp7p16yotLU0jRozQd999F3PrRs2sXLlSixYtinY3bIlMAvb2wQcfaMKECWrfvr3q1KmjFi1a6KabbtIXX3zhs30ocxbNdaNmGHfDh0wC9rZjxw7deOONat26tZKTk9WoUSNdddVVevXVV322D2XOorlu1AzjbviQScC3hGh3IBYcOXJEs2fPVosWLXTxxRdrw4YNlbb9+uuvddVVVyk1NVVz587ViRMn9OCDD+rTTz/V+++/r1q1asXMuqvjscceU0lJSdjXEwtWrlyp7du3a9KkSdHuiu2QycCRScSiBx54QBs3btSNN96oTp06KTc3Vw8//LB+9rOfacuWLerQoYOnbahzFs11VwcZ/wnjbviQycCRScSiAwcO6Pjx4xo5cqTS09N16tQpPf/88xo8eLD++te/auzYsZ62oc5ZNNddHWT8J4y74UMmA0cmHcagSqdPnzaHDh0yxhjzwQcfGElm+fLlPtuOGzfOJCUlmQMHDniWrVu3zkgyf/3rX2Nq3cE4ceJEUO337dvnt5Zwy8zMNCNHjgzrOgYOHGgyMzPDug6nIpNVi7VMhtLJkyej3QXU0MaNG01hYaHXsi+++MK43W4zfPhwr+Whzlk01x2MWMs4425sI5NVi7VMhhLjrj2dPXvWXHzxxebCCy/0Wh6JnEVz3ZWJtYwz7toPmfQWa5kMJSePu9xOJABut1vNmjULqO3zzz+vn//852rRooVnWU5Oji644AI999xzkiRjjK6++mo1btxY3377rafdmTNn1LFjR7Vp00YnT54My7r9ycvL04gRI5SSkqL69etr5MiR+s9//lPhdgmjRo1S3bp1tWfPHg0YMED16tXT8OHDPc+Vvx/RsWPHNGrUKKWmpnpe99ixYwHVVJnly5erV69eatKkidxuty666CI9+uijFdoZY3Tvvffq3HPPVXJysq6++mrt2LHD52vu3btXN954o9LS0pScnKzLLrtMq1ev9mqzYcMGuVwuPfvss7r77rvVrFkz1alTR4MHD9ZXX33ladezZ0+tXr1aBw4ckMvlksvlCug+TQgMmYzNTP785z9X69atff5+dna2unbt6rXsH//4h7p06aKkpCSlpaVp6NChXjmTfsxahw4dtG3bNl111VVKTk7W3XffLUl6+eWXNXDgQKWnp8vtdqtNmzaaM2eOiouLK6x/yZIlat26tZKSkpSVlaV3331XPXv2VM+ePb3aFRYWasaMGTrvvPPkdruVkZGhqVOnqrCwMNhNBj8uv/zyCldunH/++Wrfvr0+++wzr+Whznio1+2P3TIuMe7aFZmMzUwy7qIm4uPjlZGRUeFYDXXGQ71uf+yWcYlx10nIpPUzybgbftxOJIS++eYbffvttxUOTEnKysrSmjVrJEkul0uPP/64OnXqpNtuu00vvPCCJGnGjBnasWOHNmzYoDp16oRl3ZUpKSnRoEGD9P7772vcuHFq27atXn75ZY0cOdJn+7Nnz6pv377q3r27HnzwQSUnJ/tsZ4zRL37xC7333nu67bbb1K5dO7344ouVvm6gHn30UbVv316DBw9WQkKCXn31Vf32t79VSUmJxo8f72k3ffp03XvvvRowYIAGDBigjz76SH369NGZM2e8Xu/w4cO6/PLLderUKf3ud79Tw4YN9cQTT2jw4MH65z//qeuuu86r/X333SeXy6U777xT3377rRYtWqScnBx98sknSkpK0h/+8Afl5+fr66+/1sKFCyVJdevWrVHNCB6ZrCiambz55pt1yy236IMPPtCll17q+d0DBw5oy5Yt+tOf/uRZdt999+mee+7RTTfdpF//+tf67rvvtHjxYl111VX6+OOPVb9+fU/bvLw89e/fX0OHDtWvfvUrNW3aVJK0YsUK1a1bV1OmTFHdunX15ptvavr06SooKPBa16OPPqoJEyboyiuv1OTJk7V//35de+21atCggc4991xPu5KSEg0ePFjvvfeexo4dq3bt2unTTz/VwoUL9cUXX+ill16q0TaEf8YYHT58WO3bt/csi1TGa7Luytgx4xLjrpOQSetnknEXwTp58qR++OEH5efn65VXXtG//vUv3XzzzZ7nw5nxUK27MnbMuMS4a3dk8iexkEnG3QiI8JXfMc/f7QNKn3vyyScrPHfHHXcYSeb06dOeZX/961+NJPOPf/zDbNmyxcTHx5tJkyZFZN3lPf/880aSWbRokWdZcXGx6dWrV4V1jhw50kgy06ZNq/A6I0eO9PpI0UsvvWQkmfnz53uWnT171lx55ZU1+ijHqVOnKizr27evad26tefnb7/91tSqVcsMHDjQlJSUeJbffffdRpLXx6smTZpkJJl3333Xs+z4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", + "image/png": "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hli5cmU888wzMWfOnOKvAgAAAADoFH0LPeDss8+Os88++5DPZbPZWLZsWVx77bVx3nnnRUTEd77znRg5cmQ89thjMXPmzHjllVdi9erV8fzzz8fEiRMjIuKuu+6Kc845J2677bYYPXp0By4HAAAAACilggvEP+W1116LxsbGmDp1am7bkCFDYvLkybFhw4aYOXNmbNiwIYYOHZorDyMipk6dGuXl5bFx48b43Oc+1+51W1paoqWlJfe4ubk5IiIymUxkMpmi1nrwuGKP7y3kkJBDniwSPSGHyj7Zzj9HeXKOjuRwOGfYXUo913rC/dpVZJGQQ0IOeV2ZRTHzqdh1FXqujs4191J7aTMNgN6lpAViY2NjRESMHDmyzfaRI0fmnmtsbIwRI0a0XUTfvjFs2LDcPu908803x6JFi9ptX7t2bQwcOLBDa25oaOjQ8b2FHBJyyJNF4nDOYemkrjtXR3LYs2dPCVfSO3TWXDuc79euJouEHBJyyOuKLIqZT6tWreqyc0UUn4OZ1l7aTAOgdylpgdhZFixYEPPnz889bm5ujjFjxsS0adOiqqqqqNfMZDLR0NAQZ511VlRUVJRqqT2OHBJyyJNFoifkcMLCNZ1+jsrybCyZ2NqhHLwTob1Sz7WecL92FVkk5JCQQ15XZlHMfHppYW2XnKujc81May9tpgHQu5S0QKyuro6IiKamphg1alRue1NTU0yYMCG3z44dO9oct3///ti5c2fu+HeqrKyMysrKdtsrKio6/B9ApXiN3kAOCTnkySJxOOfQcqCsy87VkRwO1/y6U2fNtcP5fu1qskjIISGHvK7Iopj5VOyaip2FxebgPmovbaYB0LsU/CnMf8q4ceOiuro61q1bl9vW3NwcGzdujJqamoiIqKmpiV27dsWmTZty+zz55JPR2toakydPLuVyAAAAAIAOKvgdiG+99Vb86le/yj1+7bXX4sUXX4xhw4bFMcccE1dccUXceOON8cEPfjDGjRsX1113XYwePTrOP//8iIg4/vjj49Of/nRcdtllsWLFishkMjFv3ryYOXOmT2AGAAAAgMNMwQXiz372szj99NNzjw/+vovZs2fHAw88EFdffXW8/fbbMWfOnNi1a1ecdtppsXr16ujfv3/umIceeijmzZsXZ555ZpSXl8eMGTPizjvvLMHlAAAAAAClVHCB+KlPfSqy2Wzq82VlZbF48eJYvHhx6j7Dhg2L+vr6Qk8NAAAAAHSxkv4ORAAAAACgd1EgAgAAAACpFIgAAAAAQCoFIgAAAACQSoEIAAAAAKRSIAIAAAAAqRSIAAAAAEAqBSIAAAAAkEqBCAAAAACkUiACAAAAAKkUiAAAAABAKgUiAAAAAJBKgQgAAAAApFIgAgAAAACpFIgAAAAAQCoFIgAAAACQSoEIAAAAAKRSIAIAAAAAqRSIAAAAAEAqBSIAAAAAkEqBCAAAAACkUiACAAAAAKkUiAAAAABAKgUiAAAAAJBKgQgAAAAApFIgAgAAAACpFIgAAAAAQCoFIgAAAACQSoEIAAAAAKTq290LAOhNjr3m8YKP+c9bpnfCSgAAAKA0vAMRAAAAAEilQAQAAAAAUikQAQAAAIBUCkQAAAAAIJUCEQAAAABI1a2fwrx8+fL4xje+EY2NjfHRj3407rrrrpg0aVJ3LgkAAOhix17zeHcvAQD4E7rtHYjf/e53Y/78+XHDDTfEz3/+8/joRz8atbW1sWPHju5aEgAAAADwDt32DsTbb789LrvssrjkkksiImLFihXx+OOPx7e//e245ppr2uzb0tISLS0tuce7d++OiIidO3dGJpMp6vyZTCb27NkTv//976OioqLIq+j55JCQQ54sEsXm0Hf/2wWf6/e//33BxxR7roLP0ZqNPXtaO3Q/vPnmmxERkc1mS7m0Hq3Uc83/b/NkkZBDQg55XZlFV8ynYnV0rplp7aXNtObm5u5aEgDv0sG/q9/NXCvLdsP027dvXwwcODC+//3vx/nnn5/bPnv27Ni1a1f84Ac/aLP/woULY9GiRV28SgBK6Y033oijjz66u5dxWDDXAHo2My3PTAPo+d7NXOuWAnH79u3xvve9L9avXx81NTW57VdffXU8/fTTsXHjxjb7v/O7Wq2trbFz584YPnx4lJWVFbWG5ubmGDNmTLzxxhtRVVVV3IX0AnJIyCFPFgk5JEqRQzabjTfffDNGjx4d5eU+uyui9HPN/Zoni4QcEnLIk0WiozmYae35Wq3zyCFPFgk5JOSQ15VzrVs/ROXdqqysjMrKyjbbhg4dWpLXrqqqes/fcBFyOEgOebJIyCHR0RyGDBlSwtX0fJ0119yvebJIyCEhhzxZJDqSg5nWlq/VOp8c8mSRkENCDnldMde65dtmRx11VPTp0yeamprabG9qaorq6uruWBIAAAAAcAjdUiD269cvTjnllFi3bl1uW2tra6xbt67NjzQDAAAAAN2r236Eef78+TF79uyYOHFiTJo0KZYtWxZvv/127lOZO1tlZWXccMMN7d5u/14jh4Qc8mSRkENCDj2DP6c8WSTkkJBDniwScugZ/Dkl5JAni4QcEnLI68osuuVDVA765je/Gd/4xjeisbExJkyYEHfeeWdMnjy5u5YDAAAAALxDtxaIAAAAAMDhrVt+ByIAAAAA0DMoEAEAAACAVApEAAAAACCVAhEAAAAASNWrC8Tly5fHscceG/3794/JkyfHc8899yf3f+SRR+K4446L/v37x4knnhirVq3qopV2rkJy+Na3vhV/9Vd/FUceeWQceeSRMXXq1D+bW09R6P1w0MMPPxxlZWVx/vnnd+4Cu0ihOezatSvq6upi1KhRUVlZGR/60Ifek//fiIhYtmxZfPjDH44BAwbEmDFj4sorr4y9e/d20Wo7xzPPPBPnnntujB49OsrKyuKxxx77s8c89dRT8bGPfSwqKyvjL//yL+OBBx7o9HVipv0xcy1hriXMtYSZZqb1NOZawkzLM9cS5lrCXDsM51q2l3r44Yez/fr1y37729/Ovvzyy9nLLrssO3To0GxTU9Mh9//pT3+a7dOnT3bp0qXZLVu2ZK+99tpsRUVFdvPmzV288tIqNIeLLroou3z58uwLL7yQfeWVV7Jf/OIXs0OGDMn++te/7uKVl1ahORz02muvZd/3vvdl/+qv/ip73nnndc1iO1GhObS0tGQnTpyYPeecc7I/+clPsq+99lr2qaeeyr744otdvPLSKzSLhx56KFtZWZl96KGHsq+99lp2zZo12VGjRmWvvPLKLl55aa1atSr7ta99Lftv//Zv2YjIPvroo39y/1dffTU7cODA7Pz587NbtmzJ3nXXXdk+ffpkV69e3TULfo8y0/LMtYS5ljDXEmZawkzrOcy1hJmWZ64lzLWEuZY43OZary0QJ02alK2rq8s9PnDgQHb06NHZm2+++ZD7X3jhhdnp06e32TZ58uTsP/zDP3TqOjtboTm80/79+7ODBw/OPvjgg521xC5RTA779+/PfuITn8j+z//5P7OzZ8/uFQOp0Bzuueee7Pvf//7svn37umqJXabQLOrq6rJnnHFGm23z58/PnnrqqZ26zq70bobS1Vdfnf3IRz7SZtvnP//5bG1tbSeuDDMtz1xLmGsJcy1hprVnph3ezLWEmZZnriXMtYS51t7hMNd65Y8w79u3LzZt2hRTp07NbSsvL4+pU6fGhg0bDnnMhg0b2uwfEVFbW5u6f09QTA7vtGfPnshkMjFs2LDOWmanKzaHxYsXx4gRI+LSSy/timV2umJy+OEPfxg1NTVRV1cXI0eOjBNOOCFuuummOHDgQFctu1MUk8UnPvGJ2LRpU+6t86+++mqsWrUqzjnnnC5Z8+GiN/5debgz0/LMtYS5ljDXEmZa8Xrr35WHO3MtYablmWsJcy1hrhWvs/+u7FuSVznM/O53v4sDBw7EyJEj22wfOXJk/J//838OeUxjY+Mh929sbOy0dXa2YnJ4p6985SsxevTodjdhT1JMDj/5yU/ivvvuixdffLELVtg1isnh1VdfjSeffDJmzZoVq1atil/96lfxpS99KTKZTNxwww1dsexOUUwWF110Ufzud7+L0047LbLZbOzfvz/+63/9r/HVr361K5Z82Ej7u7K5uTn+7//9vzFgwIBuWlnvZablmWsJcy1hriXMtOKZad3DXEuYaXnmWsJcS5hrxevsudYr34FIadxyyy3x8MMPx6OPPhr9+/fv7uV0mTfffDMuvvji+Na3vhVHHXVUdy+nW7W2tsaIESPi3nvvjVNOOSU+//nPx9e+9rVYsWJFdy+tyz311FNx0003xd133x0///nP49/+7d/i8ccfjyVLlnT30oB3yVwz18y1hJkGPd97daZFmGt/zFxLmGtdo1e+A/Goo46KPn36RFNTU5vtTU1NUV1dfchjqqurC9q/Jygmh4Nuu+22uOWWW+KJJ56Ik046qTOX2ekKzeE//uM/4j//8z/j3HPPzW1rbW2NiIi+ffvG1q1b4wMf+EDnLroTFHM/jBo1KioqKqJPnz65bccff3w0NjbGvn37ol+/fp265s5STBbXXXddXHzxxfFf/st/iYiIE088Md5+++2YM2dOfO1rX4vy8vfG92PS/q6sqqryTo1OYqblmWsJcy1hriXMtOKZad3DXEuYaXnmWsJcS5hrxevsudYrU+zXr1+ccsopsW7duty21tbWWLduXdTU1BzymJqamjb7R0Q0NDSk7t8TFJNDRMTSpUtjyZIlsXr16pg4cWJXLLVTFZrDcccdF5s3b44XX3wx989nP/vZOP300+PFF1+MMWPGdOXyS6aY++HUU0+NX/3qV7mBHBHxi1/8IkaNGtUjh9FBxWSxZ8+edoPn4KBOfqfte0Nv/LvycGem5ZlrCXMtYa4lzLTi9da/Kw935lrCTMsz1xLmWsJcK16n/11Zko9iOQw9/PDD2crKyuwDDzyQ3bJlS3bOnDnZoUOHZhsbG7PZbDZ78cUXZ6+55prc/j/96U+zffv2zd52223ZV155JXvDDTdkKyoqsps3b+6uSyiJQnO45ZZbsv369ct+//vfz/72t7/N/fPmm2921yWURKE5vFNv+VSvQnPYtm1bdvDgwdl58+Zlt27dml25cmV2xIgR2RtvvLG7LqFkCs3ihhtuyA4ePDj7r//6r9lXX301u3bt2uwHPvCB7IUXXthdl1ASb775ZvaFF17IvvDCC9mIyN5+++3ZF154Ifv6669ns9ls9pprrslefPHFuf1fffXV7MCBA7NXXXVV9pVXXskuX74826dPn+zq1au76xLeE8y0PHMtYa4lzLWEmZYw03oOcy1hpuWZawlzLWGuJQ63udZrC8RsNpu96667ssccc0y2X79+2UmTJmWfffbZ3HOf/OQns7Nnz26z//e+973shz70oWy/fv2yH/nIR7KPP/54F6+4cxSSw9ixY7MR0e6fG264oesXXmKF3g9/rLcMpGy28BzWr1+fnTx5craysjL7/ve/P/v1r389u3///i5edecoJItMJpNduHBh9gMf+EC2f//+2TFjxmS/9KUvZf/whz90/cJL6N///d8P+f/5g9c+e/bs7Cc/+cl2x0yYMCHbr1+/7Pvf//7s/fff3+Xrfi8y0/LMtYS5ljDXEmaamdbTmGsJMy3PXEuYawlz7fCba2XZ7Hvo/ZwAAAAAQEF65e9ABAAAAABKQ4EIAAAAAKRSIAIAAAAAqRSIAAAAAEAqBSIAAAAAkEqBCAAAAACkUiACAAAAAKkUiAAAAABAKgUiAAAAAJBKgQgAAAAApFIgAgAAAACp/h+BBZXxGK5mQAAAAABJRU5ErkJggg==", "text/plain": [ - "
" + "
" ] }, "metadata": {}, @@ -628,15 +568,26 @@ "# plot a histogram of ending risk levels for each run\n", "# combine into a grid of plots\n", "import matplotlib.pyplot as plt\n", - "fig, ax = plt.subplots(5, 6, sharex='col', sharey='row', figsize=(18,13))\n", + "fig, ax = plt.subplots(2, 3, sharex='col', sharey='row', figsize=(16,8))\n", "\n", "for run in last_step.RunId.unique():\n", " run_last_step = last_step[last_step.RunId == run]\n", - " plot_location = ax[int(run/6), int(run % 6)]\n", - " run_last_step.risk_level.hist(ax=plot_location, bins=20)\n", + " print(run_last_step.risk_level.describe())\n", + " plot_location = ax[int(run/3), int(run % 3)]\n", + " run_last_step.risk_level.hist(ax=plot_location, bins=25, range=[0,1])\n", " # use grid size and risk adjustment strategy to title the plot\n", " grid_size = run_last_step.iloc[0].grid_size\n", - " plot_location.set_title(\"%dx%d grid, %s\" % (grid_size, grid_size, run_last_step.iloc[0].risk_adjustment))" + " # plot_location.set_title(\"%dx%d grid, %s\" % (grid_size, grid_size, run_last_step.iloc[0].risk_adjustment))" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "68b57146-09c9-475c-adb8-104f44c0c4e1", + "metadata": {}, + "outputs": [], + "source": [ + "fig.savefig(\"riskybet_batch_300k.png\")" ] } ], From dd5cf361e85600a1d9f06105dba8720f9ad99e70 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Thu, 3 Aug 2023 14:53:10 -0400 Subject: [PATCH 057/141] Update tests to match changes to batch run options --- tests/test_batch_run.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/test_batch_run.py b/tests/test_batch_run.py index ba21cf8..e7d90d9 100644 --- a/tests/test_batch_run.py +++ b/tests/test_batch_run.py @@ -16,15 +16,19 @@ @patch("simulatingrisk.batch_run.batch_run") def test_riskybet_batch_run(mock_batch_run): # assert mesa batch run is called as expected + # FIXME: this test is too brittle, + # as written has to be updated everytime we change batch run options results = riskybet_batch_run() mock_batch_run.assert_called_with( RiskyBetModel, parameters={ - "grid_size": [10, 20, 30], # 100], - "risk_adjustment": ["adopt", "average"], + "grid_size": 30, # [10, 20, 30], # 100], + # "grid_size": [10, 20, 30], # 100], + # "risk_adjustment": ["adopt", "average"], + "risk_adjustment": "adopt", }, iterations=5, - max_steps=100, + max_steps=3000, number_processes=1, # set None to use all available; set 1 for jupyter data_collection_period=1, display_progress=True, @@ -40,8 +44,8 @@ def test_riskyfood_batch_run(mock_batch_run): mock_batch_run.assert_called_with( RiskyFoodModel, parameters={"n": 110, "mode": "types"}, - iterations=10, - max_steps=100, + iterations=5, + max_steps=1000, number_processes=1, # set None to use all available; set 1 for jupyter data_collection_period=1, display_progress=True, From 6479205ecfd77e582ebd03452ca77605e8e47bc8 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 8 Aug 2023 11:43:12 -0400 Subject: [PATCH 058/141] Tweak risky bet model for running with solara / jupyterviz --- simulatingrisk/risky_bet/run.py | 35 +++----------- simulatingrisk/risky_bet/server.py | 74 +++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 31 deletions(-) diff --git a/simulatingrisk/risky_bet/run.py b/simulatingrisk/risky_bet/run.py index 736d2b4..fde3a74 100644 --- a/simulatingrisk/risky_bet/run.py +++ b/simulatingrisk/risky_bet/run.py @@ -1,31 +1,15 @@ import mesa from simulatingrisk.risky_bet.model import RiskyBetModel, divergent_colors -from simulatingrisk.risky_bet.server import agent_portrayal +from simulatingrisk.risky_bet.server import ( + agent_portrayal, + grid_size, + model_params, + risk_bins, +) from simulatingrisk.charts.histogram import RiskHistogramModule -grid_size = 20 - - -# make model parameters user-configurable -model_params = { - "grid_size": grid_size, # mesa.visualization.StaticText(value=grid_size), - # "grid_size": mesa.visualization.Slider( - # "Grid size", - # value=20, - # min_value=10, - # max_value=100, - # description="Grid dimension (n*n = number of agents)", - # ), - "risk_adjustment": mesa.visualization.Choice( - "Risk adjustment strategy", - value="adopt", - choices=["adopt", "average"], - description="How agents update their risk level", - ), -} - grid = mesa.visualization.CanvasGrid(agent_portrayal, grid_size, grid_size, 500, 500) risk_chart = mesa.visualization.ChartModule( @@ -49,15 +33,8 @@ ) -# generate bins for histogram, capturing 0-0.5 and 0.95-1.0 -risk_bins = [] -r = 0.05 -while r < 1.05: - risk_bins.append(round(r, 2)) - r += 0.1 histogram = RiskHistogramModule(risk_bins, 175, 500, "risk levels") - server = mesa.visualization.ModularServer( RiskyBetModel, [grid, histogram, world_chart, risk_chart], diff --git a/simulatingrisk/risky_bet/server.py b/simulatingrisk/risky_bet/server.py index 1dd8863..c6f217a 100644 --- a/simulatingrisk/risky_bet/server.py +++ b/simulatingrisk/risky_bet/server.py @@ -1,3 +1,8 @@ +import mesa +import solara +from matplotlib.figure import Figure + + def risk_index(risk_level): """Calculate a risk bin index for a given risk level. Risk levels range from 0.0 to 1.0, @@ -24,7 +29,7 @@ def agent_portrayal(agent): # initial display portrayal = { "Shape": "circle", - "Color": "gray", + "Color": "tab:gray", "Filled": "true", "Layer": 0, "r": 0.5, @@ -33,7 +38,7 @@ def agent_portrayal(agent): # color based on risk level, with ten bins # convert 0.0 to 1.0 to 1 - 10 color_index = math.floor(agent.risk_level * 10) - portrayal["Color"] = divergent_colors[color_index] + portrayal["Color"] = "rgb:%s" % divergent_colors[color_index] # size based on wealth within current distribution max_wealth = agent.model.max_agent_wealth @@ -47,3 +52,68 @@ def agent_portrayal(agent): # results in a 404 for a local custom url return portrayal + + +grid_size = 20 + + +# make model parameters user-configurable +model_params = { + "grid_size": grid_size, # mesa.visualization.StaticText(value=grid_size), + # "grid_size": mesa.visualization.Slider( + # "Grid size", + # value=20, + # min_value=10, + # max_value=100, + # description="Grid dimension (n*n = number of agents)", + # ), + "risk_adjustment": mesa.visualization.Choice( + "Risk adjustment strategy", + value="adopt", + choices=["adopt", "average"], + description="How agents update their risk level", + ), +} + +jupyterviz_params = { + # "grid_size": grid_size, + "grid_size": { + "type": "SliderInt", + "value": 20, + "label": "Grid Size", + "min": 10, + "max": 100, + "step": 1, + }, + "risk_adjustment": { + "type": "Select", + "value": "adopt", + "values": ["adopt", "average"], + "description": "How agents update their risk level", + }, +} + +# generate bins for histogram, capturing 0-0.5 and 0.95-1.0 +risk_bins = [] +r = 0.05 +while r < 1.05: + risk_bins.append(round(r, 2)) + r += 0.1 + + +# jupyter histogram based on mesa tutorial + + +def make_histogram(viz): + # Note: you must initialize a figure using this method instead of + # plt.figure(), for thread safety purpose + fig = Figure() + ax = fig.subplots() + # generate a histogram of risk levels + risk_levels = [agent.risk_level for agent in viz.model.schedule.agents] + # Note: you have to use Matplotlib's OOP API instead of plt.hist + # because plt.hist is not thread-safe. + ax.hist(risk_levels, bins=risk_bins) + # You have to specify the dependencies as follows, so that the figure + # auto-updates when viz.model or viz.df is changed. + solara.FigureMatplotlib(fig, dependencies=[viz.model, viz.df]) From fecdcbee278967ca670fd00b0ea9c39d025c9d31 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 22 Aug 2023 12:02:26 -0400 Subject: [PATCH 059/141] Set risky bet to run in jupyter or mesa runserver; document in readme --- simulatingrisk/risky_bet/README.md | 28 ++++++++++++++++++---------- simulatingrisk/risky_bet/app.py | 19 +++++++++++++++++++ simulatingrisk/risky_bet/server.py | 17 ++++++++++++----- 3 files changed, 49 insertions(+), 15 deletions(-) create mode 100644 simulatingrisk/risky_bet/app.py diff --git a/simulatingrisk/risky_bet/README.md b/simulatingrisk/risky_bet/README.md index d873e73..9e008bc 100644 --- a/simulatingrisk/risky_bet/README.md +++ b/simulatingrisk/risky_bet/README.md @@ -2,8 +2,8 @@ ## Summary -Game: agents start with random fixed risk attitudes (similar to the -risky food simulation), and decide whether or not to make a risky bet. +Game: agents start with random fixed risk attitudes (similar to the +risky food simulation), and decide whether or not to make a risky bet. Every ten rounds, agents adjust their risk attitude based on the relative wealth of their neighbors. @@ -20,18 +20,26 @@ END ROUND EVERY 10 ROUNDS, adjust risk attitudes: - Each agent looks at their neighbors (4). -- If anyone has more money, either adopt their risk attitude or average between current risk attitude and theirs (configurable via a model intialization parameter). +- If anyone has more money, either adopt their risk attitude or average between current risk attitude and theirs (configurable via a model intialization parameter). - Reset wealth back to the initial value ($1000). -Collect data to track how the distribution of risk attitudes changes over time. -Visualize a grid using a divergent color spectrum with for risk levels; use -eleven bins, with bins for 0 - 0.05 and 0.95 - 1.0, since risk = 0, 0.5, and 1 +Collect data to track how the distribution of risk attitudes changes over time. +Visualize a grid using a divergent color spectrum with for risk levels; use +eleven bins, with bins for 0 - 0.05 and 0.95 - 1.0, since risk = 0, 0.5, and 1 are all special cases we want clearly captured. ## Running the simulation - Install python dependencies as described in the main project readme (requires mesa) -- To run from the main `simulating-risk` project directory: - - Configure python to include the current directory in import path; - for C-based shells, run `setenv PYTHONPATH .` ; for bash, run `export $PYTHONPATH=.` - - To run interactively with mesa runserver: `mesa runserver simulatingrisk/risky_bet/` +- Use the main `simulating-risk` project directory as your working directory +- To run with `mesa runserver` from the command line: + - Configure python to include the current directory in import path; + for C-based shells, run `setenv PYTHONPATH .` ; for bash, run `export $PYTHONPATH=.` + - To run interactively with mesa runserver: `mesa runserver simulatingrisk/risky_bet/` +- To run with `solara` from the commandline: + - `solara run --host localhost simulatingrisk/risky_bet/app.py` +- To run in a Jupyter notebook or Colab, import the `JupyterViz` page object: +```python +from simulatingrisk.risky_bet.app import page +page +``` diff --git a/simulatingrisk/risky_bet/app.py b/simulatingrisk/risky_bet/app.py new file mode 100644 index 0000000..b573b4b --- /dev/null +++ b/simulatingrisk/risky_bet/app.py @@ -0,0 +1,19 @@ +# solara/jupyterviz app +from mesa.experimental import JupyterViz + +from simulatingrisk.risky_bet.model import RiskyBetModel +from simulatingrisk.risky_bet.server import ( + agent_portrayal, + jupyterviz_params, + make_histogram, +) + +page = JupyterViz( + RiskyBetModel, + jupyterviz_params, + measures=[make_histogram], + name="Risky Bet", + agent_portrayal=agent_portrayal, +) +# required to render the visualization with Jupyter/Solara +page diff --git a/simulatingrisk/risky_bet/server.py b/simulatingrisk/risky_bet/server.py index c6f217a..4f6e019 100644 --- a/simulatingrisk/risky_bet/server.py +++ b/simulatingrisk/risky_bet/server.py @@ -29,23 +29,30 @@ def agent_portrayal(agent): # initial display portrayal = { "Shape": "circle", - "Color": "tab:gray", + "Color": "gray", # runserver + "color": "tab:gray", # solara / jupyter "Filled": "true", "Layer": 0, - "r": 0.5, + "r": 0.5, # for runserver + # for solara / juypiterviz + "size": 25, } # color based on risk level, with ten bins # convert 0.0 to 1.0 to 1 - 10 color_index = math.floor(agent.risk_level * 10) - portrayal["Color"] = "rgb:%s" % divergent_colors[color_index] + portrayal["color"] = "%s" % divergent_colors[color_index] + # runserver requires uppercase; duplicate for now + portrayal["Color"] = portrayal["color"] # size based on wealth within current distribution max_wealth = agent.model.max_agent_wealth wealth_index = math.floor(agent.wealth / max_wealth * 10) - # set radius based on wealth, but don't go smaller than 0.1 radius + # set radius based on wealth, but don't go smaller than 1 radius # or too large to fit in the grid - portrayal["r"] = wealth_index / 15 + 0.1 + portrayal["r"] = (wealth_index / 15) + 0.1 + # size for solara / jupyterviz + portrayal["size"] = (wealth_index / 15) * 50 # TODO: change shape based on number of times risk level has been adjusted? # can't find a list of available shapes; setting to triangle and square From f6508aca67f8a7ef7d2ac84191d7f94ca6fe1f12 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 22 Aug 2023 12:14:51 -0400 Subject: [PATCH 060/141] Add matplotlib to dependencies --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 27ec664..39f4617 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ classifiers = [ ] dependencies = [ "mesa>=2.1", + "matplotlib" ] dynamic = ["version", "readme"] From f6a28a316154824281b7a8696d5665c2945aaa8f Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 22 Aug 2023 14:43:07 -0400 Subject: [PATCH 061/141] Setup risky food to run with jupyterviz; generalize risk histogram plot --- simulatingrisk/charts/histogram.py | 34 ++++++++++++++++++++++++++ simulatingrisk/risky_bet/app.py | 10 +++----- simulatingrisk/risky_bet/server.py | 38 ++++++----------------------- simulatingrisk/risky_food/app.py | 19 +++++++++++++++ simulatingrisk/risky_food/server.py | 34 ++++++++++++++++++++++++++ 5 files changed, 98 insertions(+), 37 deletions(-) create mode 100644 simulatingrisk/risky_food/app.py diff --git a/simulatingrisk/charts/histogram.py b/simulatingrisk/charts/histogram.py index 692ae2a..1b1bfbc 100644 --- a/simulatingrisk/charts/histogram.py +++ b/simulatingrisk/charts/histogram.py @@ -1,5 +1,8 @@ import os import numpy as np +import solara +from matplotlib.figure import Figure + from mesa.visualization.ModularVisualization import VisualizationElement, CHART_JS_FILE @@ -7,6 +10,8 @@ # histogram chart from mesa tutorial # https://mesa.readthedocs.io/en/stable/tutorials/adv_tutorial_legacy.html class RiskHistogramModule(VisualizationElement): + """histogram plot of agent risk levels; for use with mesa runserver""" + package_includes = [CHART_JS_FILE] local_includes = ["histogram.js"] # javascript is located in the same file as this python file @@ -27,3 +32,32 @@ def render(self, model): # generate a histogram of risk levels based on the specified bins hist = np.histogram(risk_levels, bins=self.bins)[0] return [int(x) for x in hist] + + +# generate bins for histogram, capturing 0-0.5 and 0.95-1.0 +risk_bins = [] +r = 0.05 +while r < 1.05: + risk_bins.append(round(r, 2)) + r += 0.1 + + +def plot_risk_histogram(viz): + """histogram plot of agent risk levels; for use with jupyterviz/solara""" + + # adapted from mesa visualiation tutorial + # https://mesa.readthedocs.io/en/stable/tutorials/visualization_tutorial.html#Building-your-own-visualization-component + + # Note: you must initialize a figure using this method instead of + # plt.figure(), for thread safety purpose + fig = Figure() + ax = fig.subplots() + # generate a histogram of current risk levels + risk_levels = [agent.risk_level for agent in viz.model.schedule.agents] + # Note: you have to use Matplotlib's OOP API instead of plt.hist + # because plt.hist is not thread-safe. + ax.hist(risk_levels, bins=risk_bins) + ax.set_title("risk levels") + # You have to specify the dependencies as follows, so that the figure + # auto-updates when viz.model or viz.df is changed. + solara.FigureMatplotlib(fig, dependencies=[viz.model, viz.df]) diff --git a/simulatingrisk/risky_bet/app.py b/simulatingrisk/risky_bet/app.py index b573b4b..ec76d45 100644 --- a/simulatingrisk/risky_bet/app.py +++ b/simulatingrisk/risky_bet/app.py @@ -2,16 +2,14 @@ from mesa.experimental import JupyterViz from simulatingrisk.risky_bet.model import RiskyBetModel -from simulatingrisk.risky_bet.server import ( - agent_portrayal, - jupyterviz_params, - make_histogram, -) +from simulatingrisk.risky_bet.server import agent_portrayal, jupyterviz_params +from simulatingrisk.charts.histogram import plot_risk_histogram + page = JupyterViz( RiskyBetModel, jupyterviz_params, - measures=[make_histogram], + measures=[plot_risk_histogram], name="Risky Bet", agent_portrayal=agent_portrayal, ) diff --git a/simulatingrisk/risky_bet/server.py b/simulatingrisk/risky_bet/server.py index 4f6e019..943ec90 100644 --- a/simulatingrisk/risky_bet/server.py +++ b/simulatingrisk/risky_bet/server.py @@ -1,6 +1,4 @@ import mesa -import solara -from matplotlib.figure import Figure def risk_index(risk_level): @@ -28,14 +26,15 @@ def agent_portrayal(agent): # initial display portrayal = { + # styles for mesa runserver "Shape": "circle", - "Color": "gray", # runserver - "color": "tab:gray", # solara / jupyter + "Color": "gray", "Filled": "true", "Layer": 0, - "r": 0.5, # for runserver - # for solara / juypiterviz + "r": 0.5, + # styles for solara / jupyterviz "size": 25, + "color": "tab:gray", } # color based on risk level, with ten bins @@ -57,6 +56,8 @@ def agent_portrayal(agent): # TODO: change shape based on number of times risk level has been adjusted? # can't find a list of available shapes; setting to triangle and square # results in a 404 for a local custom url + # NOTE: matplotlib scatter supports different shapes/markers, + # but not in a single scatter plot; would need to be plotted in groups return portrayal @@ -99,28 +100,3 @@ def agent_portrayal(agent): "description": "How agents update their risk level", }, } - -# generate bins for histogram, capturing 0-0.5 and 0.95-1.0 -risk_bins = [] -r = 0.05 -while r < 1.05: - risk_bins.append(round(r, 2)) - r += 0.1 - - -# jupyter histogram based on mesa tutorial - - -def make_histogram(viz): - # Note: you must initialize a figure using this method instead of - # plt.figure(), for thread safety purpose - fig = Figure() - ax = fig.subplots() - # generate a histogram of risk levels - risk_levels = [agent.risk_level for agent in viz.model.schedule.agents] - # Note: you have to use Matplotlib's OOP API instead of plt.hist - # because plt.hist is not thread-safe. - ax.hist(risk_levels, bins=risk_bins) - # You have to specify the dependencies as follows, so that the figure - # auto-updates when viz.model or viz.df is changed. - solara.FigureMatplotlib(fig, dependencies=[viz.model, viz.df]) diff --git a/simulatingrisk/risky_food/app.py b/simulatingrisk/risky_food/app.py new file mode 100644 index 0000000..34cb05a --- /dev/null +++ b/simulatingrisk/risky_food/app.py @@ -0,0 +1,19 @@ +# solara/jupyterviz app +from mesa.experimental import JupyterViz + +from simulatingrisk.risky_food.model import RiskyFoodModel +from simulatingrisk.risky_food.server import ( + jupyterviz_params, + plot_total_agents, +) +from simulatingrisk.charts.histogram import plot_risk_histogram + +page = JupyterViz( + RiskyFoodModel, + jupyterviz_params, + measures=[plot_total_agents, plot_risk_histogram], + name="Risky Food", + # no agent portrayal because this model does not use a grid +) +# required to render the visualization with Jupyter/Solara +page diff --git a/simulatingrisk/risky_food/server.py b/simulatingrisk/risky_food/server.py index ca209de..8ce049a 100644 --- a/simulatingrisk/risky_food/server.py +++ b/simulatingrisk/risky_food/server.py @@ -1,4 +1,6 @@ import mesa +import solara +from matplotlib.figure import Figure from simulatingrisk.charts.histogram import RiskHistogramModule @@ -36,3 +38,35 @@ histogram = RiskHistogramModule(risk_bins, 200, 500, "risk levels") # server is initialized in run.py +# jupyterviz is initialized in app.py + + +jupyterviz_params = { + "n": { + "type": "SliderInt", + "value": 20, + "label": "Number of starting agents", + "min": 10, + "max": 50, + "step": 1, + }, + "mode": { + "type": "Select", + "value": "types", + "values": ["types", "random"], + "description": "Risk types of random risk level distribution", + }, +} + + +def plot_total_agents(viz): + """plot total agents over time to provide an indicator of population size""" + fig = Figure() + ax = fig.subplots() + # generate a line plot of total number of agents + model_df = viz.model.datacollector.get_model_vars_dataframe() + ax.plot(model_df.num_agents) + ax.set_title("total agents") + # You have to specify the dependencies as follows, so that the figure + # auto-updates when viz.model or viz.df is changed. + solara.FigureMatplotlib(fig, dependencies=[viz.model, viz.df]) From 7c258ec932e0e8ff412e5059000c6e185d2e1272 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 22 Aug 2023 17:55:07 -0400 Subject: [PATCH 062/141] Generalize runserver histogram module; handle missing chart labels --- simulatingrisk/charts/histogram.py | 21 +++++++++++++--- simulatingrisk/risky_bet/run.py | 39 +++++++++++++++-------------- simulatingrisk/risky_food/server.py | 32 ++++++++++++++--------- simulatingrisk/utils.py | 10 ++++++++ 4 files changed, 67 insertions(+), 35 deletions(-) diff --git a/simulatingrisk/charts/histogram.py b/simulatingrisk/charts/histogram.py index 1b1bfbc..c3123b1 100644 --- a/simulatingrisk/charts/histogram.py +++ b/simulatingrisk/charts/histogram.py @@ -1,4 +1,5 @@ import os + import numpy as np import solara from matplotlib.figure import Figure @@ -9,7 +10,7 @@ # histogram chart from mesa tutorial # https://mesa.readthedocs.io/en/stable/tutorials/adv_tutorial_legacy.html -class RiskHistogramModule(VisualizationElement): +class HistogramModule(VisualizationElement): """histogram plot of agent risk levels; for use with mesa runserver""" package_includes = [CHART_JS_FILE] @@ -17,10 +18,11 @@ class RiskHistogramModule(VisualizationElement): # javascript is located in the same file as this python file local_dir = os.path.dirname(os.path.realpath(__file__)) - def __init__(self, bins, canvas_height, canvas_width, label): + def __init__(self, bins, canvas_height, canvas_width, label, agent_attr): self.canvas_height = canvas_height self.canvas_width = canvas_width self.bins = bins + self.agent_attr = agent_attr new_element = "new HistogramModule({}, {}, {}, {})" new_element = new_element.format( bins, canvas_width, canvas_height, '"%s"' % label @@ -28,12 +30,23 @@ def __init__(self, bins, canvas_height, canvas_width, label): self.js_code = "elements.push(" + new_element + ");" def render(self, model): - risk_levels = [agent.risk_level for agent in model.schedule.agents] + agent_values = [ + getattr(agent, self.agent_attr) for agent in model.schedule.agents + ] # generate a histogram of risk levels based on the specified bins - hist = np.histogram(risk_levels, bins=self.bins)[0] + hist = np.histogram(agent_values, bins=self.bins)[0] return [int(x) for x in hist] +class RiskHistogramModule(HistogramModule): + """special case: risk_level histogram""" + + def __init__(self, bins, canvas_height, canvas_width, label="risk levels"): + super().__init__( + bins, canvas_height, canvas_width, label, agent_attr="risk_level" + ) + + # generate bins for histogram, capturing 0-0.5 and 0.95-1.0 risk_bins = [] r = 0.05 diff --git a/simulatingrisk/risky_bet/run.py b/simulatingrisk/risky_bet/run.py index fde3a74..115b845 100644 --- a/simulatingrisk/risky_bet/run.py +++ b/simulatingrisk/risky_bet/run.py @@ -1,33 +1,34 @@ import mesa from simulatingrisk.risky_bet.model import RiskyBetModel, divergent_colors -from simulatingrisk.risky_bet.server import ( - agent_portrayal, - grid_size, - model_params, - risk_bins, -) -from simulatingrisk.charts.histogram import RiskHistogramModule - +from simulatingrisk.risky_bet.server import agent_portrayal, grid_size, model_params +from simulatingrisk.charts.histogram import RiskHistogramModule, risk_bins +from simulatingrisk.utils import labelLabel grid = mesa.visualization.CanvasGrid(agent_portrayal, grid_size, grid_size, 500, 500) + risk_chart = mesa.visualization.ChartModule( - [ - {"Label": "risk_min", "Color": divergent_colors[0]}, - {"Label": "risk_q1", "Color": divergent_colors[3]}, - {"Label": "risk_mean", "Color": divergent_colors[5]}, - {"Label": "risk_q3", "Color": divergent_colors[7]}, - {"Label": "risk_max", "Color": divergent_colors[-1]}, - ], + labelLabel( + [ + {"Label": "risk_min", "Color": divergent_colors[0]}, + {"Label": "risk_q1", "Color": divergent_colors[3]}, + {"Label": "risk_mean", "Color": divergent_colors[5]}, + {"Label": "risk_q3", "Color": divergent_colors[7]}, + {"Label": "risk_max", "Color": divergent_colors[-1]}, + ] + ), data_collector_name="datacollector", canvas_height=100, ) + world_chart = mesa.visualization.ChartModule( - [ - {"Label": "prob_risky_payoff", "Color": "gray"}, - {"Label": "risky_bet", "Color": "blue"}, - ], + labelLabel( + [ + {"Label": "prob_risky_payoff", "Color": "gray"}, + {"Label": "risky_bet", "Color": "blue"}, + ] + ), data_collector_name="datacollector", canvas_height=100, ) diff --git a/simulatingrisk/risky_food/server.py b/simulatingrisk/risky_food/server.py index 8ce049a..d85e96e 100644 --- a/simulatingrisk/risky_food/server.py +++ b/simulatingrisk/risky_food/server.py @@ -3,29 +3,37 @@ from matplotlib.figure import Figure from simulatingrisk.charts.histogram import RiskHistogramModule +from simulatingrisk.utils import labelLabel + chart = mesa.visualization.ChartModule( - [ - {"Label": "prob_notcontaminated", "Color": "blue"}, - {"Label": "contaminated", "Color": "red"}, - ], + labelLabel( + [ + {"Label": "prob_notcontaminated", "Color": "blue"}, + {"Label": "contaminated", "Color": "red"}, + ] + ), data_collector_name="datacollector", canvas_height=100, # default height is 200 ) risk_chart = mesa.visualization.ChartModule( - [ - {"Label": "average_risk_level", "Color": "blue"}, - {"Label": "min_risk_level", "Color": "green"}, - {"Label": "max_risk_level", "Color": "orange"}, - ], + labelLabel( + [ + {"Label": "average_risk_level", "Color": "blue"}, + {"Label": "min_risk_level", "Color": "green"}, + {"Label": "max_risk_level", "Color": "orange"}, + ] + ), data_collector_name="datacollector", canvas_height=100, ) total_agent_chart = mesa.visualization.ChartModule( - [ - {"Label": "num_agents", "Color": "gray"}, - ], + labelLabel( + [ + {"Label": "num_agents", "Color": "gray"}, + ] + ), data_collector_name="datacollector", canvas_height=100, ) diff --git a/simulatingrisk/utils.py b/simulatingrisk/utils.py index a572b8f..755b1c2 100644 --- a/simulatingrisk/utils.py +++ b/simulatingrisk/utils.py @@ -19,3 +19,13 @@ def coinflip(choices: [any, any] = [0, 1], weight: float = 0.5) -> any: # random.random is apparently faster than selection = 0 if random.random() < weight else 1 return choices[selection] + + +def labelLabel(fields): + # some kind of a bug in current (forked) version of Mesa or + # a conflict with tornado version on field labels; + # display is undefined without lowercase but server requires uppercase + # for now, just copy them + for item in fields: + item["label"] = item["Label"] + return fields From b586d32a98635cf20e86a1465c355dadfe04ba90 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 22 Aug 2023 17:58:02 -0400 Subject: [PATCH 063/141] Preliminary implemntation of hawk/dove game --- simulatingrisk/hawkdove/README.md | 44 +++++++++ simulatingrisk/hawkdove/model.py | 155 ++++++++++++++++++++++++++++++ simulatingrisk/hawkdove/run.py | 31 ++++++ simulatingrisk/hawkdove/server.py | 52 ++++++++++ tests/test_hawkdove.py | 109 +++++++++++++++++++++ 5 files changed, 391 insertions(+) create mode 100644 simulatingrisk/hawkdove/README.md create mode 100644 simulatingrisk/hawkdove/model.py create mode 100644 simulatingrisk/hawkdove/run.py create mode 100644 simulatingrisk/hawkdove/server.py create mode 100644 tests/test_hawkdove.py diff --git a/simulatingrisk/hawkdove/README.md b/simulatingrisk/hawkdove/README.md new file mode 100644 index 0000000..b723af2 --- /dev/null +++ b/simulatingrisk/hawkdove/README.md @@ -0,0 +1,44 @@ +# Hawk-Dove with risk attitudes + +Hawk/Dove game with variable risk attitudes + +## Game description + +This is a variant of the Hawk/Dove Game: https://en.wikipedia.org/wiki/Chicken_(game) + +| | H | D| +|-|-|-| +| H | 0, 0 | 3, 1| +| D |1, 3| 2, 2| + +BACKGROUND: An unpublished paper by Blessenohl shows that the equilibrium in this game is different for EU maximizers than for REU maximizers (all with the same risk-attitude), and that REU maximizers do better as a population (basically, play DOVE more often) + +We want to know: what happens when different people have _different_ risk-attitudes. + +GAME: Hawk-Dove with risk-attitudes + +Players arranged on a lattice [try both 4 neighbors (AYBD) and 8 neighbors (XYZABCDE)] + +| | | | +|-|-|-| +| X | Y |Z | +|A | **I** | B | +| C | D | E | + + +Each player on a lattice (grid in Mesa): +- Has parameter `r` [from 0 to 8, or 0 to 4 for four neighbors] +- Let `h` be the number of neighbors who played HAWK during the previous round. If `h` > `r`, then play DOVE. Otherwise play HAWK. + - [TODO: make sure comparison and risk attitude is defined consistently with other simulations] + - Choice for the first round could be randomly determined, or add parameters to see how initial conditions matter? + - [OR VARY FIRST ROUND: what proportion starts as HAWK + - [Who is a HAWK and who is a DOVE is randomly determined; proportion set at the beginning of each simulation. E.g. 30% are HAWKS; if we have 100 players, then each player has a 30% chance of being HAWK] + - Call this initial parameter HAWK-ODDS +- Payoffs are determined as follows: + - Look at what each neighbor did, then: + - If I play HAWK and neighbor plays DOVE: 3 + - If I play DOVE and neighbor plays DOVE: 2 + - If I play DOVE and neighbor plays HAWK: 1 + - If I play HAWK and neighbor plays HAWK: 0 + + diff --git a/simulatingrisk/hawkdove/model.py b/simulatingrisk/hawkdove/model.py new file mode 100644 index 0000000..80d2c5f --- /dev/null +++ b/simulatingrisk/hawkdove/model.py @@ -0,0 +1,155 @@ +from enum import Enum +import math + +import mesa + +from simulatingrisk.utils import coinflip + +Play = Enum("Play", ["HAWK", "DOVE"]) +play_choices = [Play.HAWK, Play.DOVE] + + +# divergent color scheme, nine colors +# from https://colorbrewer2.org/?type=diverging&scheme=RdYlGn&n=9 +divergent_colors_9 = [ + "#d73027", + "#f46d43", + "#fdae61", + "#fee08b", + "#ffffbf", + "#d9ef8b", + "#a6d96a", + "#66bd63", + "#1a9850", +] + +# divergent color scheme, ficolors +# from https://colorbrewer2.org/?type=diverging&scheme=RdYlGn&n=5 +divergent_colors_5 = ["#d7191c", "#fdae61", "#ffffbf", "#a6d96a", "#1a9641"] + + +class HawkDoveAgent(mesa.Agent): + """ + An agent with a risk attitude playing Hawk or Dove + """ + + def __init__(self, unique_id, model): + super().__init__(unique_id, model) + + self.points = 0 + self.choice = self.initial_choice() + self.last_choice = None + + # risk level + # - based partially on neighborhood size, + # which is configurable at the model level + num_neighbors = 8 if self.model.include_diagonals else 4 + self.risk_level = self.random.randint(0, num_neighbors) + + def initial_choice(self): + # first round : choose what to play randomly or based on initial setup + return coinflip(play_choices) + + @property + def neighbors(self): + # use configured neighborhood (with or without diagonals) on the model; + # don't include the current agent + return self.model.grid.get_neighbors( + self.pos, moore=self.model.include_diagonals, include_center=False + ) + + @property + def num_hawk_neighbors(self): + """count how many neighbors played HAWK on the last round""" + return len([n for n in self.neighbors if n.last_choice == Play.HAWK]) + + def choose(self): + "decide what to play this round" + # after the first round, choose based on what neighbors did last time + if self.model.schedule.steps > 0: + # store previous choice + self.last_choice = self.choice + + # TODO: how to make risk attitude consistent with other sims? + # agent with r = 0 should always take the risky choice + # (any risk is acceptable). + # agent with r = max should always take the safe option + # (no risk is acceptable) + if self.risk_level < self.num_hawk_neighbors: + self.choice = Play.HAWK + else: + self.choice = Play.DOVE + + def play(self): + # play against each neighbor and calculate cumulative payoff + payoff = 0 + for n in self.neighbors: + payoff += self.payoff(n) + # update total points based on payoff this round + self.points += payoff + + def payoff(self, other): + """ + If I play HAWK and neighbor plays DOVE: 3 + If I play DOVE and neighbor plays DOVE: 2 + If I play DOVE and neighbor plays HAWK: 1 + If I play HAWK and neighbor plays HAWK: 0 + """ + if self.choice == Play.HAWK: + if other.choice == Play.DOVE: + return 3 + if other.choice == Play.HAWK: + return 0 + elif self.choice == Play.DOVE: + if other.choice == Play.DOVE: + return 2 + if other.choice == Play.HAWK: + return 1 + + @property + def points_rank(self): + if self.points: + return math.floor(self.points / self.model.max_agent_points * 10) + return 0 + + +class HawkDoveModel(mesa.Model): + """ """ + + running = True # required for batch run + + def __init__(self, grid_size, include_diagonals=True): + super().__init__() + # assume a fully-populated square grid + self.num_agents = grid_size * grid_size + # mesa get_neighbors supports moore neighborhood (include diagonals) + # and von neumann (exclude diagonals) + self.include_diagonals = include_diagonals + + # initialize a single grid (each square inhabited by a single agent); + # configure the grid to wrap around so everyone has neighbors + self.grid = mesa.space.SingleGrid(grid_size, grid_size, True) + self.schedule = mesa.time.StagedActivation(self, ["choose", "play"]) + + for i in range(self.num_agents): + agent = HawkDoveAgent(i, self) + self.schedule.add(agent) + # place randomly in an empty spot + self.grid.move_to_empty(agent) + + self.datacollector = mesa.DataCollector( + model_reporters={}, + agent_reporters={"risk_level": "risk_level", "choice": "choice"}, + ) + + def step(self): + """ + A model step. Used for collecting data and advancing the schedule + """ + self.schedule.step() + self.datacollector.collect(self) + + @property + def max_agent_points(self): + # what is the current largest point total of any agent? + return max([a.points for a in self.schedule.agents]) diff --git a/simulatingrisk/hawkdove/run.py b/simulatingrisk/hawkdove/run.py new file mode 100644 index 0000000..23bd920 --- /dev/null +++ b/simulatingrisk/hawkdove/run.py @@ -0,0 +1,31 @@ +import mesa + +from simulatingrisk.hawkdove.model import HawkDoveModel +from simulatingrisk.hawkdove.server import ( + agent_portrayal, + grid_size, + model_params, + # risk_bins, +) +from simulatingrisk.charts.histogram import HistogramModule + + +risk_histogram = HistogramModule(list(range(9)), 175, 500, "risk levels", "risk_level") +points_histogram = HistogramModule( + list(range(10)), 45, 200, "cumulative payoff percentile", "points_rank" +) + +grid = mesa.visualization.CanvasGrid(agent_portrayal, grid_size, grid_size, 500, 500) + + +server = mesa.visualization.ModularServer( + HawkDoveModel, + # [grid, histogram, world_chart, risk_chart], + [grid, risk_histogram, points_histogram], + "Hawk/Dove risk attitude Simulation", + model_params=model_params, +) +server.port = 8521 # The default +server.launch() + +server.launch() diff --git a/simulatingrisk/hawkdove/server.py b/simulatingrisk/hawkdove/server.py new file mode 100644 index 0000000..6536e1f --- /dev/null +++ b/simulatingrisk/hawkdove/server.py @@ -0,0 +1,52 @@ +""" +Configure visualization elements and instantiate a server +""" + +from simulatingrisk.hawkdove.model import Play + + +def agent_portrayal(agent): + from simulatingrisk.hawkdove.model import divergent_colors_9, divergent_colors_5 + + # initial display + portrayal = { + # styles for mesa runserver + "Shape": "circle", + # "Color": "gray", + # "Filled": "true", + "Layer": 0, + "r": 0.2, + "risk_level": agent.risk_level, + "choice": str(agent.choice) + # styles for solara / jupyterviz + # "size": 25, + # "color": "tab:gray", + } + + # color based on risk level + if agent.model.include_diagonals: + colors = divergent_colors_9 + else: + colors = divergent_colors_5 + portrayal["Color"] = colors[agent.risk_level] + + # filled for hawks, hollow for doves + # (shapes would be better...) + portrayal["Filled"] = agent.choice == Play.HAWK + + # size based on points within current distribution after first round + if agent.points > 0: + # # set radius based on relative points, but don't go smaller than 1 radius + # # or too large to fit in the grid + portrayal["r"] = (agent.points_rank / 15) + 0.2 + # # size for solara / jupyterviz TODO + # portrayal["size"] = (wealth_index / 15) * 50 + + return portrayal + + +grid_size = 10 + +model_params = { + "grid_size": grid_size, +} diff --git a/tests/test_hawkdove.py b/tests/test_hawkdove.py new file mode 100644 index 0000000..e5e7d09 --- /dev/null +++ b/tests/test_hawkdove.py @@ -0,0 +1,109 @@ +import math +from unittest.mock import Mock, patch +from collections import Counter + +from simulatingrisk.hawkdove.model import HawkDoveModel, HawkDoveAgent, Play + + +def test_agent_neighbors(): + # initialize model with a small grid, include diagonals + model = HawkDoveModel(3, include_diagonals=True) + # every agent should have 8 neighbors when diagonals are included + assert all([len(agent.neighbors) == 8 for agent in model.schedule.agents]) + + # every agent should have 4 neighbors when diagonals are not included + model = HawkDoveModel(3, include_diagonals=False) + assert all([len(agent.neighbors) == 4 for agent in model.schedule.agents]) + + +def test_agent_initial_choice(): + grid_size = 100 + model = HawkDoveModel(grid_size, include_diagonals=False) + # for now, initial choice is random (hawk-odds param still todo) + initial_choices = [a.choice for a in model.schedule.agents] + choice_count = Counter(initial_choices) + # default should be around a 50/50 split + half_agents = model.num_agents / 2.0 + for choice, total in choice_count.items(): + assert math.isclose(total, half_agents, rel_tol=0.05) + + +def test_num_hawk_neighbors(): + # initialize an agent with a mock model + agent = HawkDoveAgent(1, Mock()) + mock_neighbors = [ + Mock(last_choice=Play.HAWK), + Mock(last_choice=Play.HAWK), + Mock(last_choice=Play.HAWK), + Mock(last_choice=Play.DOVE), + ] + + with patch.object(HawkDoveAgent, "neighbors", mock_neighbors): + assert agent.num_hawk_neighbors == 3 + + +def test_agent_choose(): + agent = HawkDoveAgent(1, Mock()) + # on the first round, nothing should happen (uses initial choice) + agent.model.schedule.steps = 0 + agent.choose() + assert agent.last_choice is None + + # on subsequent rounds, choose based on neighbors and risk level + agent.model.schedule.steps = 1 + + # given a specified number of hawk neighbors and risk level + with patch.object(HawkDoveAgent, "num_hawk_neighbors", 3): + # an agent with `r=0` will always take the risky choice + # (any risk is acceptable). + agent.risk_level = 0 + agent.choose() + assert agent.choice == Play.HAWK + + # risk level 2 with 3 hawks will play hawk + # (but this doesn't really make sense...) + agent.risk_level = 2 + agent.choose() + assert agent.choice == Play.HAWK + + # risk level three will not + agent.risk_level = 3 + agent.choose() + assert agent.choice == Play.DOVE + + # agent with risk level 8 will always play dove + agent.risk_level = 8 + agent.choose() + assert agent.choice == Play.DOVE + + # test last choice is updated when choose runs + agent.choice = "foo" # set to confirm stored + agent.choose() + assert agent.last_choice == "foo" + + +def test_agent_payoff(): + # If I play HAWK and neighbor plays DOVE: 3 + # If I play DOVE and neighbor plays DOVE: 2 + # If I play DOVE and neighbor plays HAWK: 1 + # If I play HAWK and neighbor plays HAWK: 0 + + agent = HawkDoveAgent(1, Mock()) + other_agent = HawkDoveAgent(2, Mock()) + # If I play HAWK and neighbor plays DOVE: 3 + agent.choice = Play.HAWK + other_agent.choice = Play.DOVE + assert agent.payoff(other_agent) == 3 + # inverse: play DOVE and neighbor plays HAWK: 1 + assert other_agent.payoff(agent) == 1 + + # if both play hawk, payoff is zero for both + other_agent.choice = Play.HAWK + assert agent.payoff(other_agent) == 0 + assert other_agent.payoff(agent) == 0 + + # if both play dove, payoff is two for both + agent.choice = Play.DOVE + other_agent.choice = Play.DOVE + assert agent.payoff(other_agent) == 2 + assert other_agent.payoff(agent) == 2 From f4deea4d27b1eaaffeb6c120a1adc6a5efdd200f Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 22 Aug 2023 18:04:36 -0400 Subject: [PATCH 064/141] Preliminary solara / jupyterviz app for hawkdove sim --- simulatingrisk/hawkdove/app.py | 17 +++++++++++++++++ simulatingrisk/hawkdove/model.py | 2 +- simulatingrisk/hawkdove/server.py | 24 ++++++++++++++++++++++-- 3 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 simulatingrisk/hawkdove/app.py diff --git a/simulatingrisk/hawkdove/app.py b/simulatingrisk/hawkdove/app.py new file mode 100644 index 0000000..0c22be7 --- /dev/null +++ b/simulatingrisk/hawkdove/app.py @@ -0,0 +1,17 @@ +# solara/jupyterviz app +from mesa.experimental import JupyterViz + +from simulatingrisk.hawkdove.model import HawkDoveModel +from simulatingrisk.hawkdove.server import agent_portrayal, jupyterviz_params + + +page = JupyterViz( + HawkDoveModel, + jupyterviz_params, + measures=[], + # measures=[plot_risk_histogram], + name="Hawk/Dove with risk attitudes", + agent_portrayal=agent_portrayal, +) +# required to render the visualization with Jupyter/Solara +page diff --git a/simulatingrisk/hawkdove/model.py b/simulatingrisk/hawkdove/model.py index 80d2c5f..123f72e 100644 --- a/simulatingrisk/hawkdove/model.py +++ b/simulatingrisk/hawkdove/model.py @@ -138,7 +138,7 @@ def __init__(self, grid_size, include_diagonals=True): self.grid.move_to_empty(agent) self.datacollector = mesa.DataCollector( - model_reporters={}, + model_reporters={"max_agent_points": "max_agent_points"}, agent_reporters={"risk_level": "risk_level", "choice": "choice"}, ) diff --git a/simulatingrisk/hawkdove/server.py b/simulatingrisk/hawkdove/server.py index 6536e1f..6b21163 100644 --- a/simulatingrisk/hawkdove/server.py +++ b/simulatingrisk/hawkdove/server.py @@ -29,6 +29,8 @@ def agent_portrayal(agent): else: colors = divergent_colors_5 portrayal["Color"] = colors[agent.risk_level] + # copy to lowercase color for solara + portrayal["color"] = portrayal["Color"] # filled for hawks, hollow for doves # (shapes would be better...) @@ -39,8 +41,8 @@ def agent_portrayal(agent): # # set radius based on relative points, but don't go smaller than 1 radius # # or too large to fit in the grid portrayal["r"] = (agent.points_rank / 15) + 0.2 - # # size for solara / jupyterviz TODO - # portrayal["size"] = (wealth_index / 15) * 50 + # # size for solara / jupyterviz + portrayal["size"] = (agent.points_rank / 15) * 50 return portrayal @@ -50,3 +52,21 @@ def agent_portrayal(agent): model_params = { "grid_size": grid_size, } + + +jupyterviz_params = { + "grid_size": { + "type": "SliderInt", + "value": grid_size, + "label": "Grid Size", + "min": 10, + "max": 100, + "step": 1, + }, + # "risk_adjustment": { + # "type": "Select", + # "value": "adopt", + # "values": ["adopt", "average"], + # "description": "How agents update their risk level", + # }, +} From 2adfcba359105316036a44344beab00b0315457c Mon Sep 17 00:00:00 2001 From: Rebecca Sutton Koeser Date: Fri, 1 Sep 2023 10:11:25 -0400 Subject: [PATCH 065/141] Created using Colaboratory --- notebooks/riskybet_simulation.ipynb | 2240 +++++++++++++++++++++++++++ 1 file changed, 2240 insertions(+) create mode 100644 notebooks/riskybet_simulation.ipynb diff --git a/notebooks/riskybet_simulation.ipynb b/notebooks/riskybet_simulation.ipynb new file mode 100644 index 0000000..6d4359e --- /dev/null +++ b/notebooks/riskybet_simulation.ipynb @@ -0,0 +1,2240 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "7c1dddaaf8b24529a2a387d5e8e9549e": { + "model_module": "jupyter-vue", + "model_name": "HtmlModel", + "model_module_version": "^1.10.0", + "state": { + "_dom_classes": [], + "_events": [], + "_jupyter_vue": "IPY_MODEL_a7759b2691f740498dbf8383fe38662f", + "_model_module": "jupyter-vue", + "_model_module_version": "^1.10.0", + "_model_name": "HtmlModel", + "_view_count": null, + "_view_module": "jupyter-vue", + "_view_module_version": "^1.10.0", + "_view_name": "VueView", + "attributes": {}, + "children": [], + "class_": null, + "layout": null, + "slot": null, + "style_": "display: none", + "tag": "span", + "v_model": "!!disabled!!", + "v_on": null, + "v_slots": [] + } + }, + "c674a9dcb3b34924b63eaba7bdb475fa": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "model_module_version": "1.2.0", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "b0b331a1aead4a1fa5f008807a21b7db": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "model_module_version": "1.2.0", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "c025f33b1ebe412289cafcf230c09c9b": { + "model_module": "@jupyter-widgets/controls", + "model_name": "DescriptionStyleModel", + "model_module_version": "1.5.0", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "6c3f626351ec45de8e9e10181b982686": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "model_module_version": "1.2.0", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "772a36e1bbf24e65885cdec0661c3264": { + "model_module": "@jupyter-widgets/controls", + "model_name": "DescriptionStyleModel", + "model_module_version": "1.5.0", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "377f36c804d84d4496843e3850483199": { + "model_module": "jupyter-vuetify", + "model_name": "SheetModel", + "model_module_version": "^1.8.5", + "state": { + "_dom_classes": [], + "_events": [], + "_jupyter_vue": "IPY_MODEL_a7759b2691f740498dbf8383fe38662f", + "_metadata": null, + "_model_module": "jupyter-vuetify", + "_model_module_version": "^1.8.5", + "_model_name": "SheetModel", + "_view_count": null, + "_view_module": "jupyter-vuetify", + "_view_module_version": "^1.8.5", + "_view_name": "VuetifyView", + "attributes": {}, + "children": [ + "IPY_MODEL_648e58e6442a4066860e24f55f37ed02", + "IPY_MODEL_392ace7631c74a86a98682bb8fb9d939", + "IPY_MODEL_22ea179f062b476b9fd517f4a8123967", + "IPY_MODEL_68d92d07158b47fda572ccbe368b9e78", + "IPY_MODEL_5b1d90162e85444e96649808070e6ad5" + ], + "class_": "d-flex ma-0", + "color": null, + "dark": null, + "elevation": 0, + "height": null, + "layout": null, + "light": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "slot": null, + "style_": "flex-direction: column; align-items: stretch; row-gap: 12px;;", + "tag": null, + "tile": null, + "v_model": "!!disabled!!", + "v_on": null, + "v_slots": [], + "width": null + } + }, + "cf23e5a1b0154df6978eaf8d07ab07e7": { + "model_module": "@jupyter-widgets/controls", + "model_name": "VBoxModel", + "model_module_version": "1.5.0", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "VBoxModel", + "_view_count": 1, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "VBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_377f36c804d84d4496843e3850483199" + ], + "layout": "IPY_MODEL_c674a9dcb3b34924b63eaba7bdb475fa" + } + }, + "0537700f06894bd0b83ab1fe6766c0c5": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "model_module_version": "1.2.0", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "369e5eff69a544abb52d1f83c0e4637b": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "model_module_version": "1.2.0", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "f377cf6357774ab287bb30d21ae9abc9": { + "model_module": "jupyter-vuetify", + "model_name": "BtnModel", + "model_module_version": "^1.8.5", + "state": { + "_dom_classes": [], + "_events": [ + "click" + ], + "_jupyter_vue": "IPY_MODEL_a7759b2691f740498dbf8383fe38662f", + "_metadata": null, + "_model_module": "jupyter-vuetify", + "_model_module_version": "^1.8.5", + "_model_name": "BtnModel", + "_view_count": null, + "_view_module": "jupyter-vuetify", + "_view_module_version": "^1.8.5", + "_view_name": "VuetifyView", + "absolute": null, + "active_class": null, + "append": null, + "attributes": {}, + "block": null, + "bottom": null, + "children": [ + "Step" + ], + "class_": "", + "color": "primary", + "dark": null, + "depressed": null, + "disabled": false, + "elevation": null, + "exact": null, + "exact_active_class": null, + "fab": null, + "fixed": null, + "height": null, + "href": null, + "icon": null, + "input_value": null, + "large": null, + "layout": null, + "left": null, + "light": null, + "link": null, + "loading": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "nuxt": null, + "outlined": false, + "replace": null, + "retain_focus_on_click": null, + "right": null, + "ripple": null, + "rounded": null, + "slot": null, + "small": null, + "style_": "", + "tag": null, + "target": null, + "text": false, + "tile": null, + "to": null, + "top": null, + "type": null, + "v_model": "!!disabled!!", + "v_on": null, + "v_slots": [], + "value": null, + "width": null, + "x_large": null, + "x_small": null + } + }, + "8a88863e0f3d48f3a887a98b54d39862": { + "model_module": "jupyter-vuetify", + "model_name": "VuetifyTemplateModel", + "model_module_version": "^1.8.10", + "state": { + "_component_instances": [], + "_dom_classes": [], + "_jupyter_vue": "IPY_MODEL_a7759b2691f740498dbf8383fe38662f", + "_model_module": "jupyter-vuetify", + "_model_module_version": "^1.8.10", + "_model_name": "VuetifyTemplateModel", + "_view_count": null, + "_view_module": "jupyter-vuetify", + "_view_module_version": "^1.8.10", + "_view_name": "VuetifyView", + "components": null, + "css": null, + "data": null, + "events": [], + "layout": "IPY_MODEL_369e5eff69a544abb52d1f83c0e4637b", + "methods": null, + "template": "\n\n\n\n \n\n " + } + }, + "88783070e8ea48cba50cc2709e061bd4": { + "model_module": "@jupyter-widgets/controls", + "model_name": "PlayModel", + "model_module_version": "1.5.0", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "PlayModel", + "_playing": false, + "_repeat": false, + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "PlayView", + "description": "", + "description_tooltip": null, + "disabled": false, + "interval": 400, + "layout": "IPY_MODEL_b0b331a1aead4a1fa5f008807a21b7db", + "max": 100, + "min": 0, + "show_repeat": false, + "step": 1, + "style": "IPY_MODEL_c025f33b1ebe412289cafcf230c09c9b", + "value": 2, + "playing": true + } + }, + "2f10bad2d9a94e90806219a20111f550": { + "model_module": "@jupyter-widgets/controls", + "model_name": "IntTextModel", + "model_module_version": "1.5.0", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "IntTextModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "IntTextView", + "continuous_update": false, + "description": "Step:", + "description_tooltip": null, + "disabled": true, + "layout": "IPY_MODEL_6c3f626351ec45de8e9e10181b982686", + "step": 1, + "style": "IPY_MODEL_772a36e1bbf24e65885cdec0661c3264", + "value": 792 + } + }, + "050bc715c4774766b822bb6d6f4f5d0c": { + "model_module": "@jupyter-widgets/controls", + "model_name": "ImageModel", + "model_module_version": "1.5.0", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ImageModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ImageView", + "format": "svg+xml", + "height": "", + "layout": "IPY_MODEL_d44634af8981492589e4710bedf1f8be", + "width": "" + } + }, + "39725bb5cfa94e46be3f2374ee6263b5": { + "model_module": "@jupyter-widgets/controls", + "model_name": "ImageModel", + "model_module_version": "1.5.0", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ImageModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ImageView", + "format": "svg+xml", + "height": "", + "layout": "IPY_MODEL_b84bcd119dfb488185e04472acdd661e", + "width": "" + } + }, + "648e58e6442a4066860e24f55f37ed02": { + "model_module": "jupyter-vuetify", + "model_name": "VuetifyTemplateModel", + "model_module_version": "^1.8.10", + "state": { + "_component_instances": [], + "_dom_classes": [], + "_jupyter_vue": "IPY_MODEL_a7759b2691f740498dbf8383fe38662f", + "_model_module": "jupyter-vuetify", + "_model_module_version": "^1.8.10", + "_model_name": "VuetifyTemplateModel", + "_view_count": null, + "_view_module": "jupyter-vuetify", + "_view_module_version": "^1.8.10", + "_view_name": "VuetifyView", + "components": null, + "css": null, + "data": null, + "events": [], + "layout": "IPY_MODEL_0537700f06894bd0b83ab1fe6766c0c5", + "methods": null, + "template": "\n\n\n\n " + } + }, + "392ace7631c74a86a98682bb8fb9d939": { + "model_module": "jupyter-vuetify", + "model_name": "SliderModel", + "model_module_version": "^1.8.5", + "state": { + "_dom_classes": [], + "_events": [], + "_jupyter_vue": "IPY_MODEL_a7759b2691f740498dbf8383fe38662f", + "_metadata": null, + "_model_module": "jupyter-vuetify", + "_model_module_version": "^1.8.5", + "_model_name": "SliderModel", + "_view_count": null, + "_view_module": "jupyter-vuetify", + "_view_module_version": "^1.8.5", + "_view_name": "VuetifyView", + "append_icon": null, + "attributes": {}, + "background_color": null, + "children": [], + "class_": null, + "color": null, + "dark": null, + "dense": false, + "disabled": false, + "error": null, + "error_count": null, + "error_messages": null, + "height": null, + "hide_details": true, + "hint": null, + "id": null, + "inverse_label": null, + "label": "Grid Size", + "layout": null, + "light": null, + "loader_height": null, + "loading": null, + "max": 100, + "messages": null, + "min": 10, + "persistent_hint": null, + "prepend_icon": null, + "readonly": null, + "rules": null, + "slot": null, + "step": 1, + "style_": null, + "success": null, + "success_messages": null, + "thumb_color": null, + "thumb_label": true, + "thumb_size": null, + "tick_labels": null, + "tick_size": null, + "ticks": null, + "track_color": null, + "track_fill_color": null, + "v_model": 20, + "v_on": null, + "v_slots": [], + "validate_on_blur": null, + "value": null, + "vertical": null + } + }, + "22ea179f062b476b9fd517f4a8123967": { + "model_module": "jupyter-vuetify", + "model_name": "SelectModel", + "model_module_version": "^1.8.5", + "state": { + "_dom_classes": [], + "_events": [], + "_jupyter_vue": "IPY_MODEL_a7759b2691f740498dbf8383fe38662f", + "_metadata": null, + "_model_module": "jupyter-vuetify", + "_model_module_version": "^1.8.5", + "_model_name": "SelectModel", + "_view_count": null, + "_view_module": "jupyter-vuetify", + "_view_module_version": "^1.8.5", + "_view_name": "VuetifyView", + "append_icon": null, + "append_outer_icon": null, + "attach": null, + "attributes": {}, + "autofocus": null, + "background_color": null, + "cache_items": null, + "children": [], + "chips": null, + "class_": "", + "clear_icon": null, + "clearable": null, + "color": null, + "counter": null, + "dark": null, + "deletable_chips": null, + "dense": false, + "disable_lookup": null, + "disabled": false, + "eager": null, + "error": null, + "error_count": null, + "error_messages": null, + "filled": null, + "flat": null, + "full_width": null, + "height": null, + "hide_details": null, + "hide_selected": null, + "hint": null, + "id": null, + "item_color": null, + "item_disabled": null, + "item_text": null, + "item_value": null, + "items": [ + "adopt", + "average" + ], + "label": "label", + "layout": null, + "light": null, + "loader_height": null, + "loading": null, + "menu_props": null, + "messages": null, + "multiple": null, + "no_data_text": null, + "open_on_clear": null, + "outlined": null, + "persistent_hint": null, + "placeholder": null, + "prefix": null, + "prepend_icon": null, + "prepend_inner_icon": null, + "readonly": null, + "return_object": null, + "reverse": null, + "rounded": null, + "rules": null, + "shaped": null, + "single_line": null, + "slot": null, + "small_chips": null, + "solo": null, + "solo_inverted": null, + "style_": "", + "success": null, + "success_messages": null, + "suffix": null, + "type": null, + "v_model": "adopt", + "v_on": null, + "v_slots": [], + "validate_on_blur": null, + "value": null + } + }, + "68d92d07158b47fda572ccbe368b9e78": { + "model_module": "jupyter-vuetify", + "model_name": "SheetModel", + "model_module_version": "^1.8.5", + "state": { + "_dom_classes": [], + "_events": [], + "_jupyter_vue": "IPY_MODEL_a7759b2691f740498dbf8383fe38662f", + "_metadata": null, + "_model_module": "jupyter-vuetify", + "_model_module_version": "^1.8.5", + "_model_name": "SheetModel", + "_view_count": null, + "_view_module": "jupyter-vuetify", + "_view_module_version": "^1.8.5", + "_view_name": "VuetifyView", + "attributes": {}, + "children": [ + "IPY_MODEL_f377cf6357774ab287bb30d21ae9abc9", + "IPY_MODEL_8a88863e0f3d48f3a887a98b54d39862", + "IPY_MODEL_88783070e8ea48cba50cc2709e061bd4", + "IPY_MODEL_2f10bad2d9a94e90806219a20111f550" + ], + "class_": "d-flex ma-0", + "color": null, + "dark": null, + "elevation": 0, + "height": null, + "layout": null, + "light": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "slot": null, + "style_": "flex-direction: row; align-items: stretch; justify-content: start; column-gap: 12px;;", + "tag": null, + "tile": null, + "v_model": "!!disabled!!", + "v_on": null, + "v_slots": [], + "width": null + } + }, + "5b1d90162e85444e96649808070e6ad5": { + "model_module": "jupyter-vuetify", + "model_name": "HtmlModel", + "model_module_version": "^1.8.10", + "state": { + "_dom_classes": [], + "_events": [], + "_jupyter_vue": "IPY_MODEL_a7759b2691f740498dbf8383fe38662f", + "_model_module": "jupyter-vuetify", + "_model_module_version": "^1.8.10", + "_model_name": "HtmlModel", + "_view_count": null, + "_view_module": "jupyter-vuetify", + "_view_module_version": "^1.8.10", + "_view_name": "VuetifyView", + "attributes": {}, + "children": [ + "IPY_MODEL_050bc715c4774766b822bb6d6f4f5d0c", + "IPY_MODEL_39725bb5cfa94e46be3f2374ee6263b5" + ], + "class_": "", + "layout": null, + "slot": null, + "style_": "display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); grid-column-gap: 10px; grid-row-gap: 10px; align-items: stretch; justify-items: stretch", + "tag": "div", + "v_model": "!!disabled!!", + "v_on": null, + "v_slots": [] + } + }, + "d44634af8981492589e4710bedf1f8be": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "model_module_version": "1.2.0", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "b84bcd119dfb488185e04472acdd661e": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "model_module_version": "1.2.0", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + } + } + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Risky bet simulation" + ], + "metadata": { + "id": "tNeScd2t_beT" + } + }, + { + "cell_type": "markdown", + "source": [ + "## setup\n", + "\n", + "install dependencies" + ], + "metadata": { + "id": "PAjn7zZ__d55" + } + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "id": "D-I9oQ1ct-PI" + }, + "outputs": [], + "source": [ + "# install forked version of mesa for now, to get local enhancements for jupyterviz\n", + "%%capture\n", + "%pip uninstall --no-input mesa\n", + "%pip install git+https://github.com/Princeton-CDH/mesa.git@expand-jupyterviz#egg=mesa\n", + "#%pip install git+https://github.com/Princeton-CDH/mesa.git@583f20beb7efb15b15758573555ce7e74f3c8333#egg=mesa\n", + "\n", + "# install simulating risk code from github\n", + "%pip install git+https://github.com/Princeton-CDH/simulating-risk.git@main#egg=simulatingrisk" + ] + }, + { + "cell_type": "markdown", + "source": [ + "## run the simulation\n" + ], + "metadata": { + "id": "NojJqcFC_oXs" + } + }, + { + "cell_type": "code", + "source": [ + "from simulatingrisk.risky_bet.app import page\n", + "\n", + "\n", + "page" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 707, + "referenced_widgets": [ + "7c1dddaaf8b24529a2a387d5e8e9549e", + "c674a9dcb3b34924b63eaba7bdb475fa", + "b0b331a1aead4a1fa5f008807a21b7db", + "c025f33b1ebe412289cafcf230c09c9b", + "6c3f626351ec45de8e9e10181b982686", + "772a36e1bbf24e65885cdec0661c3264", + "377f36c804d84d4496843e3850483199", + "cf23e5a1b0154df6978eaf8d07ab07e7", + "0537700f06894bd0b83ab1fe6766c0c5", + "369e5eff69a544abb52d1f83c0e4637b", + "f377cf6357774ab287bb30d21ae9abc9", + "8a88863e0f3d48f3a887a98b54d39862", + "88783070e8ea48cba50cc2709e061bd4", + "2f10bad2d9a94e90806219a20111f550", + "050bc715c4774766b822bb6d6f4f5d0c", + "39725bb5cfa94e46be3f2374ee6263b5", + "648e58e6442a4066860e24f55f37ed02", + "392ace7631c74a86a98682bb8fb9d939", + "22ea179f062b476b9fd517f4a8123967", + "68d92d07158b47fda572ccbe368b9e78", + "5b1d90162e85444e96649808070e6ad5", + "d44634af8981492589e4710bedf1f8be", + "b84bcd119dfb488185e04472acdd661e" + ] + }, + "id": "auDMmFlQ_YYQ", + "outputId": "68647df3-9730-451f-9c30-b9159d981320" + }, + "execution_count": 2, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "Html(layout=None, style_='display: none', tag='span')" + ], + "application/vnd.jupyter.widget-view+json": { + "version_major": 2, + "version_minor": 0, + "model_id": "7c1dddaaf8b24529a2a387d5e8e9549e" + } + }, + "metadata": { + "application/vnd.jupyter.widget-view+json": { + "colab": { + "custom_widget_manager": { + "url": "https://ssl.gstatic.com/colaboratory-static/widgets/colab-cdn-widget-manager/b3e629b1971e1542/manager.min.js" + } + } + } + } + }, + { + "output_type": "display_data", + "data": { + "text/plain": [ + "Cannot show ipywidgets in text" + ], + "text/html": [ + "Cannot show widget. You probably want to rerun the code cell above (Click in the code cell, and press Shift+Enter +)." + ], + "application/vnd.jupyter.widget-view+json": { + "version_major": 2, + "version_minor": 0, + "model_id": "cf23e5a1b0154df6978eaf8d07ab07e7" + } + }, + "metadata": { + "application/vnd.jupyter.widget-view+json": { + "colab": { + "custom_widget_manager": { + "url": "https://ssl.gstatic.com/colaboratory-static/widgets/colab-cdn-widget-manager/b3e629b1971e1542/manager.min.js" + } + } + } + } + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "## data analysis\n", + "\n", + "Run preliminary data analysis on data collected by the model.\n", + "\n", + "**NOTE**: Re-run this step after re-running the simulation to get the latest data" + ], + "metadata": { + "id": "s__PSHCH_xiL" + } + }, + { + "cell_type": "code", + "source": [ + "# get model data from the data collector\n", + "# page.args[0] == viz object;\n", + "model_df = page.args[0].model.datacollector.get_model_vars_dataframe()\n", + "# convert boolean payoff to 0/1 so we can more easily plot\n", + "model_df[\"risky_bet_i\"] = model_df[\"risky_bet\"].astype(int)\n", + "model_df" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 53 + }, + "id": "zWhAdA-g2YO8", + "outputId": "ab1a9252-0ac8-439d-d48d-ec997f5b3836" + }, + "execution_count": 3, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "Empty DataFrame\n", + "Columns: [prob_risky_payoff, risky_bet, risk_min, risk_q1, risk_mean, risk_q3, risk_max, risky_bet_i]\n", + "Index: []" + ], + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
prob_risky_payoffrisky_betrisk_minrisk_q1risk_meanrisk_q3risk_maxrisky_bet_i
\n", + "
\n", + "
\n", + "\n", + "
\n", + " \n", + "\n", + " \n", + "\n", + " \n", + "
\n", + "\n", + "
\n", + "
\n" + ] + }, + "metadata": {}, + "execution_count": 3 + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "### state of the world for this run\n", + "\n", + "- what was the probability that the bet would pay off? (blue trendline)\n", + "- when did it actually pay off? (orange bar indicates payoff)" + ], + "metadata": { + "id": "AsbyUc_P_8gi" + } + }, + { + "cell_type": "code", + "source": [ + "import altair as alt\n", + "\n", + "base = alt.Chart(model_df.reset_index()).mark_line().encode(\n", + " x='index', # alt.X('index').title(\"round\"),\n", + " y='prob_risky_payoff',\n", + ").properties(\n", + " width=800,\n", + " height=200\n", + ")\n", + "\n", + "actual_n = base.mark_bar(color=\"orange\", opacity=0.7, width=3).encode(\n", + " # x=alt.X('index').title(\"round\"),\n", + " y='risky_bet_i')\n", + "# combine the two charts vertically\n", + "alt.vconcat(base, actual_n.properties(width=800,height=50))" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 340 + }, + "id": "VLPQXTtA4zGK", + "outputId": "bb491be0-8e30-4af4-843c-6987413fa9b2" + }, + "execution_count": 4, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/html": [ + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.VConcatChart(...)" + ] + }, + "metadata": {}, + "execution_count": 4 + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "### agent risk attitudes" + ], + "metadata": { + "id": "CyqDiun_ATjs" + } + }, + { + "cell_type": "code", + "source": [ + "# get agent data from model collected data\n", + "agent_df = page.args[0].model.datacollector.get_agent_vars_dataframe()\n", + "agent_df = agent_df.reset_index() # reset index so we can access by step\n", + "agent_df" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 424 + }, + "id": "qiwnsSa_5Tk-", + "outputId": "dd6c7191-2729-49dd-a0e5-154d0eaba0be" + }, + "execution_count": 10, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + " Step AgentID risk_level choice\n", + "0 1 0 0.056868 Bet.RISKY\n", + "1 1 1 0.249539 Bet.RISKY\n", + "2 1 2 0.393241 Bet.RISKY\n", + "3 1 3 0.463799 Bet.RISKY\n", + "4 1 4 0.409122 Bet.RISKY\n", + "... ... ... ... ...\n", + "281595 704 395 0.918397 Bet.SAFE\n", + "281596 704 396 0.922623 Bet.SAFE\n", + "281597 704 397 0.913196 Bet.SAFE\n", + "281598 704 398 0.900144 Bet.SAFE\n", + "281599 704 399 0.900144 Bet.SAFE\n", + "\n", + "[281600 rows x 4 columns]" + ], + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
StepAgentIDrisk_levelchoice
0100.056868Bet.RISKY
1110.249539Bet.RISKY
2120.393241Bet.RISKY
3130.463799Bet.RISKY
4140.409122Bet.RISKY
...............
2815957043950.918397Bet.SAFE
2815967043960.922623Bet.SAFE
2815977043970.913196Bet.SAFE
2815987043980.900144Bet.SAFE
2815997043990.900144Bet.SAFE
\n", + "

281600 rows × 4 columns

\n", + "
\n", + "
\n", + "\n", + "
\n", + " \n", + "\n", + " \n", + "\n", + " \n", + "
\n", + "\n", + "\n", + "
\n", + " \n", + "\n", + "\n", + "\n", + " \n", + "
\n", + "
\n", + "
\n" + ] + }, + "metadata": {}, + "execution_count": 10 + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "#### last round\n", + "\n", + "what is the state of things at the last round run?" + ], + "metadata": { + "id": "EJqnV949AbvD" + } + }, + { + "cell_type": "code", + "source": [ + "# get data for the last round\n", + "last_step_n = max(agent_df.Step)\n", + "last_step = agent_df[agent_df.Step == last_step_n]\n", + "last_step.head(10)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 363 + }, + "id": "2XG0t9q76igQ", + "outputId": "41e2b4cb-383b-44e4-e53b-0b1087203153" + }, + "execution_count": 11, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + " Step AgentID risk_level choice\n", + "281200 704 0 0.845070 Bet.SAFE\n", + "281201 704 1 0.845070 Bet.SAFE\n", + "281202 704 2 0.686226 Bet.SAFE\n", + "281203 704 3 0.845070 Bet.SAFE\n", + "281204 704 4 0.843879 Bet.SAFE\n", + "281205 704 5 0.845939 Bet.SAFE\n", + "281206 704 6 0.845070 Bet.SAFE\n", + "281207 704 7 0.686226 Bet.SAFE\n", + "281208 704 8 0.845939 Bet.SAFE\n", + "281209 704 9 0.918397 Bet.SAFE" + ], + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
StepAgentIDrisk_levelchoice
28120070400.845070Bet.SAFE
28120170410.845070Bet.SAFE
28120270420.686226Bet.SAFE
28120370430.845070Bet.SAFE
28120470440.843879Bet.SAFE
28120570450.845939Bet.SAFE
28120670460.845070Bet.SAFE
28120770470.686226Bet.SAFE
28120870480.845939Bet.SAFE
28120970490.918397Bet.SAFE
\n", + "
\n", + "
\n", + "\n", + "
\n", + " \n", + "\n", + " \n", + "\n", + " \n", + "
\n", + "\n", + "\n", + "
\n", + " \n", + "\n", + "\n", + "\n", + " \n", + "
\n", + "
\n", + "
\n" + ] + }, + "metadata": {}, + "execution_count": 11 + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "What's the risk attitude distribution on the last round?" + ], + "metadata": { + "id": "YnKMFcY_AlxN" + } + }, + { + "cell_type": "code", + "source": [ + "# describe risk level parameter\n", + "last_step.risk_level.describe()\n" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Esqx5Vxe6usE", + "outputId": "33defa7b-7169-416f-d096-85af34440a0f" + }, + "execution_count": 12, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "count 400.000000\n", + "mean 0.880097\n", + "std 0.059203\n", + "min 0.686226\n", + "25% 0.845939\n", + "50% 0.900144\n", + "75% 0.913876\n", + "max 0.987498\n", + "Name: risk_level, dtype: float64" + ] + }, + "metadata": {}, + "execution_count": 12 + } + ] + }, + { + "cell_type": "code", + "source": [ + "# plot a histogram of risk levels on the last round\n", + "import matplotlib.pylab as plt\n", + "%matplotlib inline\n", + "\n", + "last_step.risk_level.hist(range=[0,1], bins=11)\n", + "plt.show()" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 430 + }, + "id": "PBp8lynu639g", + "outputId": "da35764b-8bd2-4e95-c792-a377d897e970" + }, + "execution_count": 13, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "
" + ], + "image/png": "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Y2O1fft2rVixQnV1dUpPT1dubq4WLFgQjjIAAECECktAGTdunFr67G16erp2794djocEAACdCH+LBwAAGIeAAgAAjENAAQAAxiGgAAAA4xBQAACAcQgoAADAOAQUAABgHAIKAAAwDgEFAAAYh4ACAACMQ0ABAADGIaAAAADjEFAAAIBxCCgAAMA4BBQAAGAcAgoAADAOAQUAABiHgAIAAIxDQAEAAMYhoAAAAOMQUAAAgHEIKAAAwDgEFAAAYBwCCgAAMA4BBQAAGIeAAgAAjENAAQAAxiGgAAAA4xBQAACAcQgoAADAOAQUAABgHAIKAAAwTsgDSlFRkRwOR8BtwIAB/v0XL15Ufn6+evTooe985zvKzc1VTU1NqMsAAAARLCxXUO644w6dPn3af9u7d69/X0FBgd588029/vrr2r17t06dOqWHHnooHGUAAIAIFROWRWNilJKS0my8trZWr7zyisrKyvTAAw9IktasWaOBAwdq3759uuuuu8JRDgAAiDBhuYLy2WefKS0tTf369dO0adN08uRJSdKBAwfk8/mUlZXlnztgwAD16tVLFRUV4SgFAABEoJBfQcnIyNDatWt122236fTp0youLta9996rjz/+WNXV1YqNjVVCQkLAfZKTk1VdXX3VNb1er7xer3/b4/FIknw+n3w+X0jrv7xeqNdFIPpsD/psD/psj7b22RVt2VFOp+aK+rqH4XqNbQuHZVlh/UmeO3dOvXv31vLly9W1a1c9/vjjAWFDkkaNGqUxY8boX//1X1tco6ioSMXFxc3Gy8rKFBcXF5a6AQBAaNXX1ysvL0+1tbVyu93XnBuWz6B8U0JCgm699VZVVVUpOztbly5d0rlz5wKuotTU1LT4mZXL5s+fr8LCQv+2x+NRenq6xo0b1+oBBsvn86m8vFzZ2dlyOp0hXRtX0Gd70Gd70Gd7tLXPg4retrGqzskVZWnJyKaQn9OX3wFpi7AHlAsXLujYsWOaPn26RowYIafTqR07dig3N1eSdPToUZ08eVKZmZlXXcPlcsnlcjUbdzqdYXsyCOfauII+24M+24M+26O1PnsbHTZW07mF+pwOZq2QB5R//ud/1uTJk9W7d2+dOnVKixYtUnR0tB599FF169ZNM2fOVGFhoRITE+V2uzV37lxlZmbyDR4AAOAX8oDyxRdf6NFHH9VXX32lm2++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\n" + }, + "metadata": {} + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "#### multiple rounds\n", + "\n", + "plot agent risk levels across rounds periodically" + ], + "metadata": { + "id": "gGRTfU0CA0Z7" + } + }, + { + "cell_type": "code", + "source": [ + "import matplotlib.pyplot as plt\n", + "# create a grid to plot multiple rounds\n", + "fig, ax = plt.subplots(ncols=4, nrows=4, sharex='col', sharey='row', figsize=(18,10))\n", + "\n", + "# try plotting every 10 rounds\n", + "max_plots = 4 * 4\n", + "\n", + "# iterate by tens starting with 10\n", + "for i, round in enumerate(range(10, last_step_n, 10)):\n", + " if i >= max_plots: # don't go beyond what our subplot grid can handle\n", + " break\n", + " round_data = agent_df[agent_df.Step == round]\n", + " plot_location = ax[int(i/4), int(i % 4)]\n", + " round_data.risk_level.hist(ax=plot_location, range=[0,1], bins=11)\n", + " plot_location.set_title(\"round %d\" % (round,))\n" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 853 + }, + "id": "rlCE6_dm7kvX", + "outputId": "e3f64491-7f71-48b6-90e2-cfcc523270f1" + }, + "execution_count": 14, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "
" + ], + "image/png": "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awCmiba8njx5sq688ko98MADF51HXqM6nFmNiJGenu51/8CBA4qJidFVV13lNZ6UlKQWLVp4gjQY7dq187rfsmVLSdK3337r2bckdezY0Wte69atPXNDpUmTJtVeh+n06dOexwEgkkRzXk+YMEGrV6/WH//4R3Xt2tXrMfIagGmiMa/j4+M1YMAASdJtt92mgoIC3Xbbbdq5c6cnt8lrAKaJprzeunWr/vSnP2ndunW1fo4XeY3qcGY1IkZNIXT+b/ICVVFRUe14gwYNqh23LCvofQUrOTlZR44c8RmvGktJSanrJQHARUVrXufn5+vFF1/U7Nmz9fOf/9zncfIagGmiNa/PV/WhXK+++qpnjLwGYJpoyuvHHntMN998s9LT07V//37t379fJ06ckHQuhw8ePOiZS16jOjSrEbHat2+vyspK7d2712v82LFjKioqUvv27T1jLVu29PoEcEk6c+ZMtaHn774l+ez7+PHjnt82hkq3bt30+eefq6SkxGt827ZtnscBIJJFQ17Pnz9feXl5mjhxoiZPnlztHPIagOmiIa8v5HK5VFlZqeLiYs8YeQ3AdCbn9cGDB7Vp0yalp6d7bo8++qgk6dZbb9V1113nmdutWzft3LlTlZWVXtvYtm2b4uLi1KlTp6BqgNloViNiDRkyRNK5T5A939y5cyVJQ4cO9Yx16NBBmzZt8pq3cOHCGn+TWJsBAwYoNjZWL7zwgtdvFy9cSygMHz5cFRUVWrhwoWfM5XJp0aJF6tmzp1JTU0O+TwAIJdPz+rXXXtN//dd/aeTIkZ41V4e8BmA6k/O6qKhIbrfbZ/z//u//JEk9evTwjJHXAExncl4vXLhQb775ptdtwoQJkqRnn31WS5cu9cwdPny4jh07pmXLlnnGTpw4oddff13Dhg2r9nrWiH5csxoRq2vXrho1apQWLlyooqIi9e3bVx9++KGWLFmi22+/3euTbx944AE99NBDys7OVlZWlj755BOtWbNGiYmJQe27devWeuSRRzRr1iz95Cc/0ZAhQ/Txxx/rnXfe8XubmzZt8nzDOH78uMrKyvTkk09Kkvr06eP5xNuePXvqrrvu0pQpU/TNN9/oqquu0pIlS7R//3699NJLQa0fAOqSyXn94Ycf6he/+IVatWql/v37e/3wLEk33nijrrzySknkNQDzmZzX7733nv7rv/5L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b95cx48f15w5czR06FCtXLnSM+83v/mNnnrqKb/2cd1116l3797q3LmzTp48qcWLF2vixIk6fPiwnn76ac+8I0eOqE+fPj7Pr/rN4+HDh3XttdcGVScAhFo0ZfXevXslnbsUSIcOHfTHP/5RxcXFys/P149//GP94x//8GQxWQ3ANNGU1xeqqKjQa6+9poyMDF111VVej5HXAEwTTXkdGxurv/zlL8rJydGtt97qGb/++uu1efNmtWjRwjNGXqM6nFmNiPLQQw953f/rX/8qSZo0aZLXeNUHFq5atSrofY0dO9YTutK56ydVVFTowIEDkqS1a9fqzJkzmjBhgte8iRMn+r2Pt956S4899phuu+023Xfffdq4caMGDRqkuXPn6tChQ55533//vZxOp8/zGzdu7HkcACJFNGV1aWmppHN/Prlu3Trl5OTo4Ycf1vLly/Xtt99q/vz5nrlkNQDTRFNeX2jdunU6duyYz1nVEnkNwDzRltctW7ZUt27d9Pjjj2v58uV69tlntX//ft111106ffq0Zx55jerQrEZEOf+DrSTpwIEDiomJ8TlbIikpSS1atPCEaTDatWvndb9ly5aSpG+//dazb0k+nz7bunVrz9xAORwO5ebm6uzZs3rvvfc8402aNKn2WkxVIX7+NZ0AwG7RlNVV+Tps2DA1bdrUM37DDTcoPT1dmzdv9ppLVgMwSTTl9YWWLl2qBg0a6Kc//anPY+Q1ANNEU14XFxfr5ptvVq9evTRr1izddttt+tWvfqW//OUvev/997Vo0SLPXPIa1aFZjYhSUxCd/9u8QFVUVFQ73qBBg2rHLcsKel/+SE1NlSSdOnXKM5acnKwjR474zK0aS0lJCeuaACAQ0ZTVVfl64QfASFKbNm08P7RLZDUA80RTXp/v+++/15tvvqkBAwZUm9/kNQDTRFNe/+Uvf9GxY8e8LgEiSX379lV8fLw++OADzxh5jerQrEZEa9++vSorKz3XFK1y7NgxFRUVqX379p6xli1bqqioyGvemTNnqg0+f/ctyWffx48f92peBOrf//63pHO/lazSrVs37dy5U5WVlV5zt23bpri4OHXq1Cno/QFAuJmc1ddff70k6euvv/Z57PDhw2Q1gKhicl6f76233tJ3331X7SVAJPIagPlMzutjx45J8m2WW5aliooKnT171jNGXqM6NKsR0YYMGSJJmjdvntf43LlzJUlDhw71jHXo0EGbNm3ymrdw4cIaf5tYmwEDBig2NlYvvPCC128YL1xLTU6dOuWzb7fbrdmzZ6tRo0ZeH4YwfPhwHTt2TMuWLfOMnThxQq+//rqGDRtW7TWcACBSmJzVnTt3VteuXbVixQqdOHHCM/7uu+/qq6++UlZWlmeMrAZgOpPz+nwFBQWKi4vTHXfcUe3j5DUA05mc11UN5ldffdVr/K233lJZWZn+3//7f54x8hrVaWj3AoCL6dq1q0aNGqWFCxeqqKhIffv21YcffqglS5bo9ttv92r4PvDAA3rooYeUnZ2trKwsffLJJ1qzZo0SExOD2nfr1q31yCOPaNasWfrJT36iIUOG6OOPP9Y777zj1zbfeustPfnkkxo+fLjS09N16tQpFRQUaPfu3XrqqaeUlJTkmTt8+HDdcMMNGj16tD777DMlJibqxRdfVEVFhfLz84NaPwDUFZOzWpKee+45ZWVlqXfv3nrwwQdVXFysuXPnqlOnTnr44Yc988hqAKYzPa+lcyeEvPPOO8rOzvb6rIHzkdcATGdyXg8bNkw//OEPNWPGDB04cEA33HCD9u3bp9///vdKTk7W/fff75lLXqNaFhABpk+fbkmyjh8/7vOY2+228vPzrfT0dCs2NtZKTU21pkyZYp0+fdprXkVFhTV58mQrMTHRiouLswYNGmTt27fPat++vTVq1CjPvEWLFlmSrI8++sjr+Rs2bLAkWRs2bPDaZn5+vpWcnGw1adLEyszMtHbv3u2zzeps377dGjZsmHX55ZdbjRo1spo2bWr17t3b+vOf/1zt/FOnTln333+/1apVKysuLs7q27evzxoBwE7RmNVVCgsLrRtuuMFq3LixlZCQYP385z+3jhw54jOPrAZggmjO6wULFliSrLfeeuui88hrACaI1rw+deqUlZuba3Xq1MlyOp1WYmKiNWLECOvf//53tXPJa5zPYVlh/jQ5AAAAAAAAAABqwTWrAQAAAAAAAAC2o1kNAAAAAAAAALAdzWoAAAAAAAAAgO1oVgMAAAAAAAAAbEezGgAAAAAAAABgO5rVAAAAAAAAAADbNbR7AcGorKzU4cOH1axZMzkcDruXAwB+sSxL3333nVJSUhQTUz9+V0heAzAReU1eAzADeU1eAzBDIHltZLP68OHDSk1NtXsZABCUr776SldccYXdy6gT5DUAk5HXAGAG8hoAzOBPXhvZrG7WrJmkcwXGx8f7/Ty32613331XAwcOVGxsbLiWZwtqMxO1mSuY+kpKSpSamurJsPqAvPZFbWaiNjMFWxt5TV5L1GYqajMTee0/8toXtZmJ2sxUF3ltZLO66k9d4uPjAw7nuLg4xcfHR+UXC7WZh9rMdSn11ac/1yOvfVGbmajNTJdaG3ldO75+zERtZqK2mpHXtePrx0zUZiZqq5k/eV0/LuoEAAAAAAAAAIhoNKsBAAAAAAAAALajWQ0AAAAAAAAAsB3NagAAAAAAAACA7WhWAwAAAAAAAABs19DuBQAAAAAAEI3SHl8Vsm05G1iakyF1yVsjV4XDM75/9tCQ7QMAALtxZjUAAAAAAAAAwHY0qwEAAAAAAAAAtqNZDQAAAAAAAACwHc1qAAAAAAAAAIDtaFYDAAAAAAAAAGxHsxoAAAAAAAAAYDua1QAAAAAAAAAA2zW0ewEAAAAAANS1tMdX2b0EAABwAc6sBgAAAAAAAADYjjOrAQAAQqguztTbP3to2PcBAAAAAHWNM6sBAAAAAAAAALajWQ0AAAAAAAAAsB3NagAAAAAAAACA7QJuVm/atEnDhg1TSkqKHA6Hli9f7nnM7XZr8uTJuvbaa3XZZZcpJSVFv/jFL3T48GGvbaSlpcnhcHjdZs+efcnFAAAAAAAAAADMFHCzuqysTF27dtX8+fN9HisvL9fOnTs1bdo07dy5U8uWLdOePXt06623+sydMWOGjhw54rlNmDAhuAoAAAAAAAAAAMZrGOgTBg8erMGDB1f7WPPmzVVYWOg19vvf/14ZGRk6ePCg2rVr5xlv1qyZkpKSAt09AAAAAAAAACAKBdysDlRxcbEcDodatGjhNT579mzNnDlT7dq1U05OjnJzc9WwYfXLcblccrlcnvslJSWSzl12xO12+72WqrmBPMcU1GYmajNXMPVF62txPvK6dtRmpkBqczawwr2ckL7GHLeanxfNyOvaUZuZIi2vQ8kZY3n9WyUajiN5XTPyunbUZiZqM1Nd5LXDsqygv0M7HA69+eabuv3226t9/PTp07rpppt09dVXa+nSpZ7xuXPnqnv37kpISNDmzZs1ZcoUjR49WnPnzq12O3l5ecrPz/cZLygoUFxcXLDLB4A6VV5erpycHBUXFys+Pt7u5YQFeQ0gGpDX5DUAM5DX5DUAMwSS12FrVrvdbmVnZ+vQoUN67733LrqQl19+WQ8++KBKS0vldDp9Hq/uN4mpqak6ceJEQN+Q3G63CgsLlZWVpdjYWL+fZwJqMxO1mSuY+kpKSpSYmBjVP0yT17WjNjMFUluXvDVhX8/uvEEh2xbHzRd5TV5L1GaqSMvrUHLGWJrZo1LTtsfIVenwjIfye4JdyOuakde1ozYzRVptofyeQF77CiSvw3IZELfbrbvvvlsHDhzQ+vXra11Ez549dfbsWe3fv1+dO3f2edzpdFbbxI6NjQ3qCzrY55mA2sxEbeYKpL5ofh2qkNf+ozYz+VObq8Jx0cdDtY5wbLM+H7cL50c78tp/1GamSMnrcHBVOrzWHk3HkLz2RV77j9rMFCm1heN7AnntPd9fIW9WVzWq9+7dqw0bNqhVq1a1PmfXrl2KiYlRmzZtQr0cAAAAAAAAAIABAm5Wl5aWat++fZ77X375pXbt2qWEhAQlJydr+PDh2rlzp95++21VVFTo6NGjkqSEhAQ1atRIW7Zs0bZt29SvXz81a9ZMW7ZsUW5uru655x61bNkydJUBAAAAAAAAMFra46vsXgLqUMDN6u3bt6tfv36e+5MmTZIkjRo1Snl5eXrrrbckSd26dfN63oYNG5SZmSmn06lXX31VeXl5crlcSk9PV25urmc7AAAAAAAAAID6J+BmdWZmpi72mYy1fV5j9+7dtXXr1kB3CwAAAAAAAACIYjF2LwAAAAAAAAAAAJrVAAAAAAAAAADb0awGAAAAAAAAANiOZjUAAAAAAAAAwHY0qwEAAAAAAAAAtqNZDQAAAAAAAACwHc1qAAAAAAAAAIDtaFYDAAAAAAAAAGxHsxoAAAAAAAAAYLuGdi8AgH/SHl8V9n3snz007PsAAAAAAAAAqsOZ1QAAAAAAAAAA29GsBgAAAAAAAADYjmY1AAAAAAAAAMB2NKsBAAAAAAAAALajWQ0AAAAAAAAAsB3NagAAAAAAAACA7WhWAwAAAAAAAABsF3CzetOmTRo2bJhSUlLkcDi0fPlyr8cty9ITTzyh5ORkNWnSRAMGDNDevXu95pw6dUojR45UfHy8WrRoofvvv1+lpaWXVAgAAAAAAAAAwFwNA31CWVmZunbtqvvuu0933nmnz+Nz5szR888/ryVLlig9PV3Tpk3ToEGD9Nlnn6lx48aSpJEjR+rIkSMqLCyU2+3W6NGjNXbsWBUUFFx6RQAAIOTSHl9V6xxnA0tzMqQueWvkqnAEvI/9s4cGs7R6yZ/j4a+ajhvHAwAAIHz4+RqoXsDN6sGDB2vw4MHVPmZZlubNm6epU6fqtttukyT98Y9/VNu2bbV8+XKNGDFC//znP7V69Wp99NFH6tGjhyTphRde0JAhQ/Tss88qJSXlEsoBAAAAAAAAAJgo4Gb1xXz55Zc6evSoBgwY4Blr3ry5evbsqS1btmjEiBHasmWLWrRo4WlUS9KAAQMUExOjbdu26Y477vDZrsvlksvl8twvKSmRJLndbrndbr/XVzU3kOeYgtrMFEhtzgZWuJcT0tc4mo+bFFx90fpanI+8rp2ptfmTQc4Yy+vfQEXyaxJpeR1KNR23SD4e/gr2/RYNtdeGvK4dtZmJvDYTeV0z8rp2ptbGz9fktYnqIq8dlmUFfcQdDofefPNN3X777ZKkzZs366abbtLhw4eVnJzsmXf33XfL4XDotdde01NPPaUlS5Zoz549Xttq06aN8vPz9fDDD/vsJy8vT/n5+T7jBQUFiouLC3b5AFCnysvLlZOTo+LiYsXHx9u9nLAgrwFEA/KavAZgBvKavAZghkDyOqRnVofLlClTNGnSJM/9kpISpaamauDAgQF9Q3K73SosLFRWVpZiY2PDsVTbUJuZAqmtS96asK9nd96gkG0rmo+bFFx9VWdBRDPyunam1uZPBjljLM3sUalp22Pkqgz8mnqhzKBQi7S8DqWajlskHw9/Bft+I6/Ja4naTEVem4m8rhl5XTtTa+Pna/LaRHWR1yFtViclJUmSjh075nVm9bFjx9StWzfPnG+++cbreWfPntWpU6c8z7+Q0+mU0+n0GY+NjQ0qiIJ9ngmozUz+1BbMhykEs45wbDNaj5sUWH3R/DpUIa/9Z1ptgWSQq9IRVGaZ8HpESl6Hw4XHzYTj4a9A32/RVHtNyGv/UZuZyGszkde+yGv/mVYbP1+fQ16bKZx5HRPMgmqSnp6upKQkrVu3zjNWUlKibdu2qVevXpKkXr16qaioSDt27PDMWb9+vSorK9WzZ89QLgcAAAAAAAAAYIiAz6wuLS3Vvn37PPe//PJL7dq1SwkJCWrXrp0mTpyoJ598Uh07dlR6erqmTZumlJQUz3Wtr7nmGt1yyy0aM2aMFixYILfbrfHjx2vEiBFKSUkJWWEAAAAAAAAAAHME3Kzevn27+vXr57lfde2kUaNGafHixXrsscdUVlamsWPHqqioSL1799bq1avVuHFjz3OWLl2q8ePHq3///oqJiVF2draef/75EJQDAAAAAAAAADBRwM3qzMxMWZZV4+MOh0MzZszQjBkzapyTkJCggoKCQHcNAAAAAAAAAIhSIb1mNQAAAAAAAAAAwQj4zGoA0Svt8VUh25azgaU5GVKXvDVen367f/bQkO0DAAAAAAAA0YMzqwEAAAAAAAAAtqNZDQAAAAAAAACwHc1qAAAAAAAAAIDtuGY1bOXPNZJruvaxv7hGMgAAAAAAABD5OLMaAAAAAAAAAGA7mtUAAAAAAAAAANvRrAYAAAAAAAAA2I5mNQAAAAAAAADAdjSrAQAAAAAAAAC2o1kNAAAAAAAAALAdzWoAAAAAAAAAgO1oVgMAAAAAAAAAbEezGgAAAAAAAABgO5rVAAAAAAAAAADb0awGAAAAAAAAANgu5M3qtLQ0ORwOn9u4ceMkSZmZmT6PPfTQQ6FeBgAAAAAAAADAIA1DvcGPPvpIFRUVnvu7d+9WVlaW7rrrLs/YmDFjNGPGDM/9uLi4UC8DAAAAAAAAAGCQkDerW7du7XV/9uzZ6tChg/r27esZi4uLU1JSUqh3DQAAAAAAAAAwVFivWX3mzBm98soruu++++RwODzjS5cuVWJiorp06aIpU6aovLw8nMsAAAAAAAAAAES4kJ9Zfb7ly5erqKhI9957r2csJydH7du3V0pKij799FNNnjxZe/bs0bJly2rcjsvlksvl8twvKSmRJLndbrndbr/XUzU3kOeYwtTanA2s2ufEWF7/BiqSX5NAjps/r1Ukqem4RfLxCEQw77loqf1iyOvamVobeU1emyjY91s01F4b8rp21GYm8tpM5HXNyOvamVobP1+T1yaqi7x2WJYVtiM+aNAgNWrUSCtXrqxxzvr169W/f3/t27dPHTp0qHZOXl6e8vPzfcYLCgq43jUAY5SXlysnJ0fFxcWKj4+3ezlhQV4DiAbkNXkNwAzkNXkNwAyB5HXYmtUHDhzQlVdeqWXLlum2226rcV5ZWZmaNm2q1atXa9CgQdXOqe43iampqTpx4kRA35DcbrcKCwuVlZWl2NhY/4sxgKm1dclbU+scZ4ylmT0qNW17jFyVjlrnX2h3XvVfV5EgkOPmz2sVSWo6bpF8PAIRzHuupKREiYmJUf3DNHldO1NrI6/JaxMF+34jr8lridpMRV6bibyuGXldO1Nr4+dr8tpEdZHXYbsMyKJFi9SmTRsNHTr0ovN27dolSUpOTq5xjtPplNPp9BmPjY0NKoiCfZ4JTKvNVeF/2LoqHQHNr2LC6+HPcQum9khw4XEz4XgEIpD3XLTVXh3y2n+m1UZen0NemynQ91s01V4T8tp/1GYm8tpM5LUv8tp/ptXGz9fnkNdmCmdeh6VZXVlZqUWLFmnUqFFq2PA/u/jiiy9UUFCgIUOGqFWrVvr000+Vm5urPn366LrrrgvHUgAAAAAAAAAABghLs3rt2rU6ePCg7rvvPq/xRo0aae3atZo3b57KysqUmpqq7OxsTZ06NRzLAAAAAAAAAAAYIizN6oEDB6q6S2GnpqZq48aN4dglAAAAAAAAAMBgMXYvAAAAAAAAAAAAmtUAAAAAAAAAANvRrAYAAAAAAAAA2I5mNQAAAAAAAADAdjSrAQAAAAAAAAC2o1kNAAAAAAAAALAdzWoAAAAAAAAAgO1oVgMAAAAAAAAAbEezGgAAAAAAAABgO5rVAAAAAAAAAADb0awGAAAAAAAAANiOZjUAAAAAAAAAwHY0qwEAAAAAAAAAtqNZDQAAAAAAAACwHc1qAAAAAAAAAIDtaFYDAAAAAAAAAGxHsxoAAAAAAAAAYDua1QAAAAAAAAAA24W8WZ2XlyeHw+F1u/rqqz2Pnz59WuPGjVOrVq3UtGlTZWdn69ixY6FeBgAAAAAAAADAIA3DsdEf/vCHWrt27X920vA/u8nNzdWqVav0+uuvq3nz5ho/frzuvPNOffDBB+FYCgAAAAAAUSvt8VVh38f+2UPDvg8AAKQwNasbNmyopKQkn/Hi4mK99NJLKigo0I9//GNJ0qJFi3TNNddo69atuuGGG8KxHAAAAACoF/xpXDobWJqTIXXJWyNXhSPgfdC4BAAA4RKWZvXevXuVkpKixo0bq1evXpo1a5batWunHTt2yO12a8CAAZ65V199tdq1a6ctW7bU2Kx2uVxyuVye+yUlJZIkt9stt9vt97qq5gbyHFOYWpuzgVX7nBjL699ARfJrEshx8+e1iiQ1HbdIPh6BCOY9Fy21Xwx5XTtTayOvyWsTBft+i4baa0Ne187U2sjr+pfXdSHcx5y8rhl5XTtTayOv619eR/Lx8Fdd5LXDsqyQHvF33nlHpaWl6ty5s44cOaL8/Hx9/fXX2r17t1auXKnRo0d7Ba0kZWRkqF+/fnr66aer3WZeXp7y8/N9xgsKChQXFxfK5QNA2JSXlysnJ0fFxcWKj4+3ezlhQV4DiAbkNXkNwAzkNXkNwAyB5HXIm9UXKioqUvv27TV37lw1adIkqGZ1db9JTE1N1YkTJwL6huR2u1VYWKisrCzFxsYGV1CEMrW2Lnlrap3jjLE0s0elpm2Pkasy8D9T3J03KJil1YlAjps/r1Ukqem4RfLxCEQw77mSkhIlJiZG9Q/T5HXtTK2NvCavTRTs+428Jq8lc2sjr+tfXteFcB9z8rpm5HXtTK2NvK5/eR3Jx8NfdZHXYbkMyPlatGihTp06ad++fcrKytKZM2dUVFSkFi1aeOYcO3as2mtcV3E6nXI6nT7jsbGxQQVRsM8zgWm1BXKNPFelI6hr6pnwevhz3IKpPRJceNxMOB6BCOQ9F221V4e89p9ptZHX55DXZgr0/RZNtdeEvPafabWR1+fUp7yuC3V1zMlrX+S1/0yrjbw+pz7ltQnHw1/hzOuwN6tLS0v1xRdf6Oc//7muv/56xcbGat26dcrOzpYk7dmzRwcPHlSvXr3CvRQEqC4+VRoAAAAAAAAApDA0qx955BENGzZM7du31+HDhzV9+nQ1aNBAP/vZz9S8eXPdf//9mjRpkhISEhQfH68JEyaoV69eNX64IgAAAAAAAAAg+oW8WX3o0CH97Gc/08mTJ9W6dWv17t1bW7duVevWrSVJzz33nGJiYpSdnS2Xy6VBgwbpxRdfDPUyAAAAAAAAAAAGCXmz+tVXX73o440bN9b8+fM1f/78UO8aAAAAAAAAAGCoGLsXAAAAAAAAAAAAzWoAAAAAAAAAgO1oVgMAAAAAAAAAbEezGgAAAAAAAABgO5rVAAAAAAAAAADb0awGAAAAAAAAANiOZjUAAAAAAAAAwHY0qwEAAAAAAAAAtqNZDQAAAAAAAACwHc1qAAAAAAAAAIDtaFYDAAAAAAAAAGxHsxoAAAAAAAAAYDua1QAAAAAAAAAA29GsBgAAAAAAAADYrqHdCwAAAEDkSXt8Vdj3sX/20LDvAwAAAIA5OLMaAAAAAAAAAGA7mtUAAAAAAAAAANuF/DIgs2bN0rJly/Svf/1LTZo00Y033qinn35anTt39szJzMzUxo0bvZ734IMPasGCBaFeDoAIUxd/Vi7xp+UAAAAAACBycJk9/4T8zOqNGzdq3Lhx2rp1qwoLC+V2uzVw4ECVlZV5zRszZoyOHDniuc2ZMyfUSwEAAAAAAAAAGCLkZ1avXr3a6/7ixYvVpk0b7dixQ3369PGMx8XFKSkpKdS7BwAAgCHCfXaJs4GlORlh3QUAAACAEAp5s/pCxcXFkqSEhASv8aVLl+qVV15RUlKShg0bpmnTpikuLq7abbhcLrlcLs/9kpISSZLb7Zbb7fZ7LVVzA3mOKcJRm7OBFbJtXQpnjOX1b6Ai+XgHctwi5Xj461KP26UK93EP5j0XyV+LoUJe187U2vzJIPL6HPI6clTVFOjXViR/LYYKeV07U2sjr8nrcIjEn62DmW8i8rp2ptZGXpPX4RANee2wLCtsr1xlZaVuvfVWFRUV6f333/eML1y4UO3bt1dKSoo+/fRTTZ48WRkZGVq2bFm128nLy1N+fr7PeEFBQY0NbgCINOXl5crJyVFxcbHi4+PtXk5YkNcAogF5TV4DMAN5TV4DMEMgeR3WZvXDDz+sd955R++//76uuOKKGuetX79e/fv31759+9ShQwefx6v7TWJqaqpOnDgR0Dckt9utwsJCZWVlKTY2NrBiIlw4auuStyYk27lUzhhLM3tUatr2GLkqHQE/f3feoDCsKjQCOW6Rcjz8danHLdJV1RfIe66kpESJiYlR/cM0eV07U2vzJ4PI63PI68gRTFZL5DV5fY6ptZHX5HU4hPuYB/t+I6/Ja8nc2shr8jocoiGvw3YZkPHjx+vtt9/Wpk2bLtqolqSePXtKUo3NaqfTKafT6TMeGxsbVBAF+zwThLI2V0Vk/Q+rq9IR1JpMONb+HLdIOx7+Cva4mSKQ95wJX4uXirz2n2m1BfI+Jq/NzLxozutA328mfC1eKvLaf6bVRl6fQ16HVl0dc/LaF3ntP9NqI6/PIa9DKxryOuTNasuyNGHCBL355pt67733lJ6eXutzdu3aJUlKTk4O9XIAAAAAAAAAAAYIebN63LhxKigo0IoVK9SsWTMdPXpUktS8eXM1adJEX3zxhQoKCjRkyBC1atVKn376qXJzc9WnTx9dd911oV4OAAAAAAAAAMAAIW9W/+EPf5AkZWZmeo0vWrRI9957rxo1aqS1a9dq3rx5KisrU2pqqrKzszV16tRQLwUAAAAAAAAAYIiwXAbkYlJTU7Vx48ZQ7xYAAAAAAAAAYLCwfcAiAAAAAAAwX9rjq8K6fWcDS3MywroLAIAhYuxeAAAAAAAAAAAAnFkNAAAAAAAAAIaLhr+E4cxqAAAAAAAAAIDtaFYDAAAAAAAAAGxHsxoAAAAAAAAAYDua1QAAAAAAAAAA29GsBgAAAAAAAADYjmY1AAAAAAAAAMB2NKsBAAAAAAAAALajWQ0AAAAAAAAAsB3NagAAAAAAAACA7WhWAwAAAAAAAABsR7MaAAAAAAAAAGA7mtUAAAAAAAAAANs1tHsBQDRIe3xVUM9zNrA0J0PqkrdGrgpHiFcFAAAAAAAAmINmNQAAAAAAAICAcfIeQs3WZvX8+fP1zDPP6OjRo+ratateeOEFZWRk2LkkAAAQxfhhGgAAAAAil23XrH7ttdc0adIkTZ8+XTt37lTXrl01aNAgffPNN3YtCQAAAAAAAABgE9ua1XPnztWYMWM0evRo/eAHP9CCBQsUFxenl19+2a4lAQAAAAAAAABsYstlQM6cOaMdO3ZoypQpnrGYmBgNGDBAW7ZssWNJAAAAAIAIwWWbAACon2xpVp84cUIVFRVq27at13jbtm31r3/9y2e+y+WSy+Xy3C8uLpYknTp1Sm632+/9ut1ulZeX6+TJk4qNjQ1y9ZEpHLU1PFsWku1cqoaVlsrLK9XQHaOKysB/4Dx58mQYVuUt2NfqUmuLZNFcm/Sf+gJ5z3333XeSJMuywrk0W5HXtTO1Nn9yjrw2U32oLdD3G3lNXkvm1kZeR3+mRXNt5LUv8rp2ptZGXkd/pkVzbWHNa8sGX3/9tSXJ2rx5s9f4o48+amVkZPjMnz59uiWJGzdu3KLi9tVXX9VV3NY58pobN27RdCOvuXHjxs2MG3nNjRs3bmbc/Mlrh2XV/a8gz5w5o7i4OL3xxhu6/fbbPeOjRo1SUVGRVqxY4TX/wt8kVlZW6tSpU2rVqpUcDv9/Q1FSUqLU1FR99dVXio+Pv+Q6Igm1mYnazBVMfZZl6bvvvlNKSopiYmz7yICwIq9rR21mojYzBVsbeU1eS9RmKmozE3ldM/K6dtRmJmozU13ktS2XAWnUqJGuv/56rVu3ztOsrqys1Lp16zR+/Hif+U6nU06n02usRYsWQe8/Pj4+6r5YqlCbmajNXIHW17x58zCuxn7ktf+ozUzUZqZgaiOvA8PXj5mozUzU5o28DgxfP2aiNjNRmzd/89qWZrUkTZo0SaNGjVKPHj2UkZGhefPmqaysTKNHj7ZrSQAAAAAAAAAAm9jWrP7pT3+q48eP64knntDRo0fVrVs3rV692udDFwEAAAAAAAAA0c+2ZrUkjR8/vtrLfoSL0+nU9OnTff6EJhpQm5mozVzRXp/dovn1pTYzUZuZorm2SBHNrzG1mYnazBTNtUWKaH6Nqc1M1GamuqjNlg9YBAAAAAAAAADgfNH5cbkAAAAAAAAAAKPQrAYAAAAAAAAA2I5mNQAAAAAAAADAdjSrAQAAAAAAAAC2o1kNBCEtLU333nuv3csAAPz/yGUAMAN5DQBmIK9hF5rVQBiVlpZq4sSJuuKKK+R0OnXNNdfoD3/4Q7Vzi4qKNHbsWLVu3VqXXXaZ+vXrp507d9bxigEgur322mu655571LFjRzkcDmVmZtY41+VyafLkyUpJSVGTJk3Us2dPFRYWVjt38+bN6t27t+Li4pSUlKT/+q//UmlpaZiqAIDo529el5aWavr06brllluUkJAgh8OhxYsX17jdf/7zn7rlllvUtGlTJSQk6Oc//7mOHz8eniIAoB7wN68/+ugjjR8/Xj/84Q912WWXqV27drr77rv1+eefVzufvK6/Gtq9ACBaVVRUaNCgQdq+fbvGjRunjh07as2aNfrlL3+pb7/9Vr/+9a89cysrKzV06FB98sknevTRR5WYmKgXX3xRmZmZ2rFjhzp27GhjJQAQPf7whz9ox44d+tGPfqSTJ09edO69996rN954QxMnTlTHjh21ePFiDRkyRBs2bFDv3r0983bt2qX+/fvrmmuu0dy5c3Xo0CE9++yz2rt3r955551wlwQAUcnfvD5x4oRmzJihdu3aqWvXrnrvvfdqnHvo0CH16dNHzZs311NPPaXS0lI9++yz+vvf/64PP/xQjRo1CkMlABDd/M3rp59+Wh988IHuuusuXXfddTp69Kh+//vfq3v37tq6dau6dOnimUte1280qxGxysrKdNlll9m9jKAtW7ZMmzdv1ksvvaT77rtPkvTwww9r+PDhmjlzph544AG1adNGkvTGG29o8+bNev311zV8+HBJ0t13361OnTpp+vTpKigosK0OAKhiei5L0p/+9CddfvnliomJ8fqB+EIffvihXn31VT3zzDN65JFHJEm/+MUv1KVLFz322GPavHmzZ+6vf/1rtWzZUu+9957i4+MlnfuzyTFjxujdd9/VwIEDw1sUAFygPuV1cnKyjhw5oqSkJG3fvl0/+tGPapz71FNPqaysTDt27FC7du0kSRkZGcrKytLixYs1duzYkNcBABdTn/J60qRJKigo8Go0//SnP9W1116r2bNn65VXXvGMk9f1G5cBQUTIy8uTw+HQZ599ppycHLVs2dJz1trZs2c1c+ZMdejQQU6nU2lpafr1r38tl8vltQ2Hw6G8vDyfbV94naXFixfL4XDogw8+0KRJkzyX3bjjjjt8/qTEsiw9+eSTuuKKKxQXF6d+/frpH//4h181/e1vf5MkjRgxwmt8xIgROn36tFasWOEZe+ONN9S2bVvdeeednrHWrVvr7rvv1ooVK3xqBYBwi8ZclqTU1FTFxNT+488bb7yhBg0aeP0g3LhxY91///3asmWLvvrqK0lSSUmJCgsLdc8993ga1dK5xnbTpk315z//2e+1AUAw6nteO51OJSUl+bXNv/zlL/rJT37iaXxI0oABA9SpUyfyGkDY1fe8vvHGG33OiO7YsaN++MMf6p///KfXOHldv9GsRkS56667VF5erqeeekpjxoyRJD3wwAN64okn1L17dz333HPq27evZs2a5dMEDtSECRP0ySefaPr06Xr44Ye1cuVKjR8/3mvOE088oWnTpqlr16565plndOWVV2rgwIEqKyurdfsul0sNGjTwCeO4uDhJ0o4dOzxjH3/8sbp37+4T8BkZGSovL6/xGk4AEG7RlMuB+Pjjj9WpUyevBrR0Lpelc5f+kKS///3vOnv2rHr06OE1r1GjRurWrZs+/vjjkK4LAGpSX/PaX19//bW++eYbn7yWzmU7eQ2grpDX/2FZlo4dO6bExETPGHkNLgOCiNK1a1evS1588sknWrJkiR544AH97//+ryTpl7/8pdq0aaNnn31WGzZsUL9+/YLaV6tWrfTuu+/K4XBIOnfd6Oeff17FxcVq3ry5jh8/rjlz5mjo0KFauXKlZ95vfvMbPfXUU7Vuv3PnzqqoqNDWrVu9rm1adcb1119/7Rk7cuSI+vTp47ON5ORkSdLhw4d17bXXBlUnAFyKaMrlQBw5csSTwec7P5er5p0/fuHcqswHgHCrr3ntr9ry+tSpU3K5XHI6nXW9NAD1DHn9H0uXLtXXX3+tGTNmeMbIa3BmNSLKQw895HX/r3/9q6Rz1zY6369+9StJ0qpVq4Le19ixYz1BLEk333yzKioqdODAAUnS2rVrdebMGU2YMMFr3sSJE/3afk5Ojpo3b6777rtPhYWF2r9/vxYuXKgXX3xRkvT999975n7//ffVBm3jxo195gJAXYqmXA6Ev7lc9W9Nc8lvAHWlvua1v2rL6/PnAEA4kdfn/Otf/9K4cePUq1cvjRo1yjNOXoNmNSJKenq61/0DBw4oJiZGV111ldd4UlKSWrRo4QnYYJx/7SNJatmypSTp22+/9exbOncNpfO1bt3aM/dikpKS9NZbb8nlcmngwIFKT0/Xo48+qhdeeEGS1LRpU8/cJk2aVHtd6tOnT3seBwA7RFMuB8LfXK76t6a55DeAulJf89pfteX1+XMAIJzIa+no0aMaOnSomjdv7vmsmCrkNbgMCCJKTYFz/m/4AlVRUVHt+PlheD7LsoLe14X69Omjf//73/r73/+usrIyde3a1fOn4506dfLMq/oU8wtVjaWkpIRsTQAQiGjLZX8lJyd7Xa6pyoW5XPXniTVlOPkNoK7U17z2V215nZCQwJ+UA6gT9T2vi4uLNXjwYBUVFelvf/ubz8/L5DU4sxoRrX379qqsrNTevXu9xo8dO6aioiK1b9/eM9ayZUsVFRV5zTtz5ky1AefvviX57Pv48eOe30L6o0GDBurWrZtuuukmNW3aVGvXrpV07pNsq3Tr1k07d+5UZWWl13O3bdumuLg4r8Y2ANgpGnLZH926ddPnn3+ukpISr/Ft27Z5HpekLl26qGHDhtq+fbvXvDNnzmjXrl2eeQBQ1+pLXvvr8ssvV+vWrX3yWpI+/PBD8hqAbepTXp8+fVrDhg3T559/rrfffls/+MEPfOaQ16BZjYg2ZMgQSdK8efO8xufOnStJGjp0qGesQ4cO2rRpk9e8hQsX1vgbxtoMGDBAsbGxeuGFF7x+63jhWgJx/PhxPf3007ruuuu8mtXDhw/XsWPHtGzZMs/YiRMn9Prrr2vYsGH81hBAxIi2XK7J8OHDVVFRoYULF3rGXC6XFi1apJ49eyo1NVWS1Lx5cw0YMECvvPKKvvvuO8/cP/3pTyotLdVdd90V8rUBgD/qS14HIjs7W2+//ba++uorz9i6dev0+eefk9cAbFNf8rqiokI//elPtWXLFr3++uvq1atXjXPJ6/qNy4AgonXt2lWjRo3SwoULVVRUpL59++rDDz/UkiVLdPvtt3t9Iu4DDzyghx56SNnZ2crKytInn3yiNWvWKDExMah9t27dWo888ohmzZqln/zkJxoyZIg+/vhjvfPOO35vs2/fvurVq5euuuoqHT16VAsXLlRpaanefvttxcT853dFw4cP1w033KDRo0frs88+U2Jiol588UVVVFQoPz8/qPUDQDiYnsubNm3y/IB//PhxlZWV6cknn5R07tJNffr0kST17NlTd911l6ZMmaJvvvlGV111lZYsWaL9+/frpZde8trmb3/7W914443q27evxo4dq0OHDul//ud/NHDgQN1yyy1B1QoAl6q+5LUk/f73v1dRUZHncnsrV67UoUOHJEkTJkxQ8+bNJUm//vWv9frrr6tfv3767//+b5WWluqZZ57Rtddeq9GjRwdVKwBcqvqS17/61a/01ltvadiwYTp16pReeeUVr+3cc889nv8mr+s5C4gA06dPtyRZx48f93nM7XZb+fn5Vnp6uhUbG2ulpqZaU6ZMsU6fPu01r6Kiwpo8ebKVmJhoxcXFWYMGDbL27dtntW/f3ho1apRn3qJFiyxJ1kcffeT1/A0bNliSrA0bNnhtMz8/30pOTraaNGliZWZmWrt37/bZZk1yc3OtK6+80nI6nVbr1q2tnJwc64svvqh27qlTp6z777/fatWqlRUXF2f17dvXZ40AUFeiNZer6qruNn36dK+533//vfXII49YSUlJltPptH70ox9Zq1evrna7f/vb36wbb7zRaty4sdW6dWtr3LhxVklJSa3rAYBLRV5bVvv27Wuc++WXX3rN3b17tzVw4EArLi7OatGihTVy5Ejr6NGjta4HAC5Vfc/rvn371jivuvYkeV1/OSwrgj8FAwAAAAAAAABQL3DNagAAAAAAAACA7WhWAwAAAAAAAABsR7MaAAAAAAAAAGA7mtUAAAAAAAAAANvRrAYAAAAAAAAA2I5mNQAAAAAAAADAdg3tXkAwKisrdfjwYTVr1kwOh8Pu5QCAXyzL0nfffaeUlBTFxNSP3xWS1wBMRF6T1wDMQF6T1wDMEEheG9msPnz4sFJTU+1eBgAE5auv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7tJLL/X4mrwGgMDzJa8//vhjdevWTVdffbVGjx6tSpUq6fvvv9f+/fs9xpHX0Y1mNSyXl5d33gaAXfztb39TnTp1FBMTc96r7iSpS5cu6tKli8e6IUOGqFmzZpo+fbpHs3rSpEnKy8vTxo0bVbduXUlSixYt1LFjR82fP99jLACEQjTl9a/96U9/0nXXXafCwkIdOXKkxOOPP/64qlWrphUrVigpKUnSmT+bHDhwoD7++GN16tQpoDUAQFmiMa+vv/56/f73v7/gGPIaQLiJprzOzc3VPffco65du+qtt95STMz5b/ZAXkc3bgOCkMrKypLD4dDWrVvVp08fVatWTW3btpUknT59Wk899ZQaNGggp9Op+vXr6/HHH1dBQYHHPhwOh7Kyskrs+9z7Kc2fP18Oh0NffPGFhg8frpo1a+qiiy7S7373uxK30jDGaMKECbr44ouVmJioDh066Ntvv/W6rvT09AsG7YXExsYqPT1dOTk5Huvffvtt3Xrrre5GtSRlZmbq8ssv19///ne/jgUA3iKvz1i1apXeeustzZgxo9THc3NztWzZMt11113uX6Ql6Z577lGlSpXIawBBR16fdfz4cZ0+fbrUx8hrAFaL9rxeuHChsrOzNXHiRMXExCgvL09FRUUlxpHXoFkNS9xxxx3Kz8/XpEmTNHDgQEnS/fffrzFjxuiaa67Rs88+q3bt2mny5Mnq3bt3uY41dOhQff311xo7dqweeughvffeexoyZIjHmDFjxmj06NG66qqr9Mwzz+iSSy5Rp06dlJeXV65jn09eXp6OHDmi77//Xs8++6w++ugj3XTTTe7Hf/zxRx06dEjNmzcvsW2LFi20adOmoMwLAM4VzXldWFiooUOH6v7771eTJk1KHbNlyxadPn26RF7Hx8eradOm5DWAkInmvJak/v37KykpSQkJCerQoYO+/PJLj8fJawDhIlrz+pNPPlFSUpJ+/PFH/eY3v1GlSpWUlJSkhx56SCdPnnSPI6/BbUBgiauuukoLFy50f/31119rwYIFuv/++/XSSy9Jkh5++GHVqlVL06ZN02effaYOHTr4dawaNWro448/lsPhkCQVFRVp5syZOnbsmKpUqaLDhw9r6tSp6tq1q9577z33uCeeeEKTJk0qZ6Wl++Mf/6gXX3xRkhQTE6Pbb79dzz33nPvxn376SZKUmppa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sWKFJk2apOeff15fffWV/vGPf+iDDz7QU089ZfXUgLBBXtsHeQ1EN/LaPshrILqR1/ZBXqNYxFxZnZycrNjYWGVnZ3usz87OVkpKSqnbpKSk+DTeKv7UVmzatGmaMmWKPvnkE/32t78N5jT94mtt33//vfbs2aNu3bq51xUVFUmSKlSooO3bt6tBgwbBnbSX/DlvqampiouLU2xsrHvdFVdcoYMHD+rUqVOKj48P6py95U9to0eP1t133637779fktSkSRPl5eVp0KBBeuKJJxQTY9/3zs6XJUlJSVz1UQryunTktXXIa0/kNYqR16Ujr61DXnsir1GMvC4deW0d8toTee0b+z4b54iPj1ezZs20fPly97qioiItX75crVq1KnWbVq1aeYyXpGXLlp13vFX8qU2Spk6dqqeeekpLlixR8+bNQzFVn/laW8OGDbVlyxZt3rzZvdx2223q0KGDNm/erPT09FBO/4L8OW9t2rTRd9995/6BI0k7duxQampq2ASz5F9t+fn5JQK4+IfQmfv225ddsiRckNclkdfWIq89kdcoRl6XRF5bi7z2RF6jGHldEnltLfLaE3ntI78/mjEMLVq0yDidTjN//nyzdetWM2jQIFO1alVz8OBBY4wxd999txk5cqR7/BdffGEqVKhgpk2bZrZt22bGjh1r4uLizJYtW6wq4bx8rW3KlCkmPj7evPXWW+ann35yL8ePH7eqhPPytbZzhfOn3/pa2w8//GAqV65shgwZYrZv327ef/99U6tWLTNhwgSrSjgvX2sbO3asqVy5snn99dfNrl27zMcff2waNGhgevXqZVUJ53X8+HGzadMms2nTJiPJTJ8+3WzatMns3bvXGGPMyJEjzd133+0ev2vXLpOYmGgee+wxs23bNjN79mwTGxtrlixZYlUJYY+8Jq/DDXlNXqN05DV5HW7Ia/IapSOvyetwQ16T1/6KqGa1McbMmjXL1K1b18THx5sWLVqYtWvXuh9r166d6devn8f4v//97+byyy838fHxplGjRuaDDz4I8Yy950tt9erVM5JKLGPHjg39xL3g63n7tXAOZ2N8r2316tWmZcuWxul0mksuucRMnDjRnD59OsSz9o4vtblcLpOVlWUaNGhgEhISTHp6unn44YfNL7/8EvqJl+Gzzz4r9fVTXE+/fv1Mu3btSmzTtGlTEx8fby655BIzb968kM/bbsjrM8jr8EFen0Fe41zk9Rnkdfggr88gr3Eu8voM8jp8kNdnkNe+cRhj8+vNAQAAAAAAAAC2FzH3rAYAAAAAAAAA2BfNagAAAAAAAACA5WhWAwAAAAAAAAAsR7MaAAAAAAAAAGA5mtUAAAAAAAAAAMvRrAYAAAAAAAAAWI5mNQAAAAAAAADAcjSrAQAAAAAAAACWo1kNAAAAAAAAALAczWoAAAAAAAAAgOVoVgMAAAAAAAAALEezGgAAAAAAAABguf8HCzIXWRqZPD8AAAAASUVORK5CYII=\n" + }, + "metadata": {} + } + ] + } + ] +} \ No newline at end of file From 8235302d3f3ec0d4f77886ec980da53a28e51edf Mon Sep 17 00:00:00 2001 From: Rebecca Sutton Koeser Date: Tue, 5 Sep 2023 13:20:37 -0400 Subject: [PATCH 066/141] Created using Colaboratory --- notebooks/riskyfood_simulation.ipynb | 2488 ++++++++++++++++++++++++++ 1 file changed, 2488 insertions(+) create mode 100644 notebooks/riskyfood_simulation.ipynb diff --git a/notebooks/riskyfood_simulation.ipynb b/notebooks/riskyfood_simulation.ipynb new file mode 100644 index 0000000..85ff289 --- /dev/null +++ b/notebooks/riskyfood_simulation.ipynb @@ -0,0 +1,2488 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "toc_visible": true, + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "55b570acd2ef4d97af85b752a4b7dcec": { + "model_module": "jupyter-vue", + "model_name": "HtmlModel", + "model_module_version": "^1.10.0", + "state": { + "_dom_classes": [], + "_events": [], + "_jupyter_vue": "IPY_MODEL_eb66bc8bbfb843b5aa6e3b001141d465", + "_model_module": "jupyter-vue", + "_model_module_version": "^1.10.0", + "_model_name": "HtmlModel", + "_view_count": null, + "_view_module": "jupyter-vue", + "_view_module_version": "^1.10.0", + "_view_name": "VueView", + "attributes": {}, + "children": [], + "class_": null, + "layout": null, + "slot": null, + "style_": "display: none", + "tag": "span", + "v_model": "!!disabled!!", + "v_on": null, + "v_slots": [] + } + }, + "0e82d3840b7048c38d79f7773fb50313": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "model_module_version": "1.2.0", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "4653195536cc47ed99889ab807e2a23a": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "model_module_version": "1.2.0", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "a1a96e523827460da8558ea4d45acf2c": { + "model_module": "@jupyter-widgets/controls", + "model_name": "DescriptionStyleModel", + "model_module_version": "1.5.0", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "2d2b516e41f048ab8abbdf64eeba3154": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "model_module_version": "1.2.0", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "b5c7e942f6cb4452ab7be0de14f84de3": { + "model_module": "@jupyter-widgets/controls", + "model_name": "DescriptionStyleModel", + "model_module_version": "1.5.0", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "" + } + }, + "d71a01b3f13a44aea387f635ce9e4fec": { + "model_module": "jupyter-vuetify", + "model_name": "SheetModel", + "model_module_version": "^1.8.5", + "state": { + "_dom_classes": [], + "_events": [], + "_jupyter_vue": "IPY_MODEL_eb66bc8bbfb843b5aa6e3b001141d465", + "_metadata": null, + "_model_module": "jupyter-vuetify", + "_model_module_version": "^1.8.5", + "_model_name": "SheetModel", + "_view_count": null, + "_view_module": "jupyter-vuetify", + "_view_module_version": "^1.8.5", + "_view_name": "VuetifyView", + "attributes": {}, + "children": [ + "IPY_MODEL_fa3ed8c4ed1249e7813920f66704105a", + "IPY_MODEL_1206cdd010d64dc299fe94af4522348d", + "IPY_MODEL_aecb01c82b194b1da17292f2b99edd2a", + "IPY_MODEL_16089329a19c46048f08cf23c8914a0f", + "IPY_MODEL_9b6d641d924647f08fcabf5d6685369d" + ], + "class_": "d-flex ma-0", + "color": null, + "dark": null, + "elevation": 0, + "height": null, + "layout": null, + "light": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "slot": null, + "style_": "flex-direction: column; align-items: stretch; row-gap: 12px;;", + "tag": null, + "tile": null, + "v_model": "!!disabled!!", + "v_on": null, + "v_slots": [], + "width": null + } + }, + "8568f5d71b3d4ff788789e1795825b55": { + "model_module": "@jupyter-widgets/controls", + "model_name": "VBoxModel", + "model_module_version": "1.5.0", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "VBoxModel", + "_view_count": 1, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "VBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_d71a01b3f13a44aea387f635ce9e4fec" + ], + "layout": "IPY_MODEL_0e82d3840b7048c38d79f7773fb50313" + } + }, + "8e1baf7101444819a0f8330008fac154": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "model_module_version": "1.2.0", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "ad8e7e2c01ab43d39ea018054fd19583": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "model_module_version": "1.2.0", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "f4d0360f79fb4982b7c17bfd8c25628e": { + "model_module": "jupyter-vuetify", + "model_name": "BtnModel", + "model_module_version": "^1.8.5", + "state": { + "_dom_classes": [], + "_events": [ + "click" + ], + "_jupyter_vue": "IPY_MODEL_eb66bc8bbfb843b5aa6e3b001141d465", + "_metadata": null, + "_model_module": "jupyter-vuetify", + "_model_module_version": "^1.8.5", + "_model_name": "BtnModel", + "_view_count": null, + "_view_module": "jupyter-vuetify", + "_view_module_version": "^1.8.5", + "_view_name": "VuetifyView", + "absolute": null, + "active_class": null, + "append": null, + "attributes": {}, + "block": null, + "bottom": null, + "children": [ + "Step" + ], + "class_": "", + "color": "primary", + "dark": null, + "depressed": null, + "disabled": false, + "elevation": null, + "exact": null, + "exact_active_class": null, + "fab": null, + "fixed": null, + "height": null, + "href": null, + "icon": null, + "input_value": null, + "large": null, + "layout": null, + "left": null, + "light": null, + "link": null, + "loading": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "nuxt": null, + "outlined": false, + "replace": null, + "retain_focus_on_click": null, + "right": null, + "ripple": null, + "rounded": null, + "slot": null, + "small": null, + "style_": "", + "tag": null, + "target": null, + "text": false, + "tile": null, + "to": null, + "top": null, + "type": null, + "v_model": "!!disabled!!", + "v_on": null, + "v_slots": [], + "value": null, + "width": null, + "x_large": null, + "x_small": null + } + }, + "feec24aa04404492a8f1ad83ccfdd0ea": { + "model_module": "jupyter-vuetify", + "model_name": "VuetifyTemplateModel", + "model_module_version": "^1.8.10", + "state": { + "_component_instances": [], + "_dom_classes": [], + "_jupyter_vue": "IPY_MODEL_eb66bc8bbfb843b5aa6e3b001141d465", + "_model_module": "jupyter-vuetify", + "_model_module_version": "^1.8.10", + "_model_name": "VuetifyTemplateModel", + "_view_count": null, + "_view_module": "jupyter-vuetify", + "_view_module_version": "^1.8.10", + "_view_name": "VuetifyView", + "components": null, + "css": null, + "data": null, + "events": [], + "layout": "IPY_MODEL_ad8e7e2c01ab43d39ea018054fd19583", + "methods": null, + "template": "\n\n\n\n \n\n " + } + }, + "1ebceeba0bac4a13b5938efae0a89280": { + "model_module": "@jupyter-widgets/controls", + "model_name": "PlayModel", + "model_module_version": "1.5.0", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "PlayModel", + "_playing": false, + "_repeat": false, + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "PlayView", + "description": "", + "description_tooltip": null, + "disabled": false, + "interval": 400, + "layout": "IPY_MODEL_4653195536cc47ed99889ab807e2a23a", + "max": 100, + "min": 0, + "show_repeat": false, + "step": 1, + "style": "IPY_MODEL_a1a96e523827460da8558ea4d45acf2c", + "value": 1, + "playing": true + } + }, + "f44fe04cba424e348aef1aea8554378e": { + "model_module": "@jupyter-widgets/controls", + "model_name": "IntTextModel", + "model_module_version": "1.5.0", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "IntTextModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "IntTextView", + "continuous_update": false, + "description": "Step:", + "description_tooltip": null, + "disabled": true, + "layout": "IPY_MODEL_2d2b516e41f048ab8abbdf64eeba3154", + "step": 1, + "style": "IPY_MODEL_b5c7e942f6cb4452ab7be0de14f84de3", + "value": 1 + } + }, + "a62668efd08a4bae8183fb2b75c8c252": { + "model_module": "@jupyter-widgets/controls", + "model_name": "ImageModel", + "model_module_version": "1.5.0", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ImageModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ImageView", + "format": "svg+xml", + "height": "", + "layout": "IPY_MODEL_f217f28e4dde401da22029e41661caf7", + "width": "" + } + }, + "a2a12ca90e1c48b696f0383411b1a50e": { + "model_module": "@jupyter-widgets/controls", + "model_name": "ImageModel", + "model_module_version": "1.5.0", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "ImageModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "ImageView", + "format": "svg+xml", + "height": "", + "layout": "IPY_MODEL_489233570c5b418b99243d088f1a3ca7", + "width": "" + } + }, + "fa3ed8c4ed1249e7813920f66704105a": { + "model_module": "jupyter-vuetify", + "model_name": "VuetifyTemplateModel", + "model_module_version": "^1.8.10", + "state": { + "_component_instances": [], + "_dom_classes": [], + "_jupyter_vue": "IPY_MODEL_eb66bc8bbfb843b5aa6e3b001141d465", + "_model_module": "jupyter-vuetify", + "_model_module_version": "^1.8.10", + "_model_name": "VuetifyTemplateModel", + "_view_count": null, + "_view_module": "jupyter-vuetify", + "_view_module_version": "^1.8.10", + "_view_name": "VuetifyView", + "components": null, + "css": null, + "data": null, + "events": [], + "layout": "IPY_MODEL_8e1baf7101444819a0f8330008fac154", + "methods": null, + "template": "\n\n\n\n " + } + }, + "1206cdd010d64dc299fe94af4522348d": { + "model_module": "jupyter-vuetify", + "model_name": "SliderModel", + "model_module_version": "^1.8.5", + "state": { + "_dom_classes": [], + "_events": [], + "_jupyter_vue": "IPY_MODEL_eb66bc8bbfb843b5aa6e3b001141d465", + "_metadata": null, + "_model_module": "jupyter-vuetify", + "_model_module_version": "^1.8.5", + "_model_name": "SliderModel", + "_view_count": null, + "_view_module": "jupyter-vuetify", + "_view_module_version": "^1.8.5", + "_view_name": "VuetifyView", + "append_icon": null, + "attributes": {}, + "background_color": null, + "children": [], + "class_": null, + "color": null, + "dark": null, + "dense": false, + "disabled": false, + "error": null, + "error_count": null, + "error_messages": null, + "height": null, + "hide_details": true, + "hint": null, + "id": null, + "inverse_label": null, + "label": "Number of starting agents", + "layout": null, + "light": null, + "loader_height": null, + "loading": null, + "max": 50, + "messages": null, + "min": 10, + "persistent_hint": null, + "prepend_icon": null, + "readonly": null, + "rules": null, + "slot": null, + "step": 1, + "style_": null, + "success": null, + "success_messages": null, + "thumb_color": null, + "thumb_label": true, + "thumb_size": null, + "tick_labels": null, + "tick_size": null, + "ticks": null, + "track_color": null, + "track_fill_color": null, + "v_model": 20, + "v_on": null, + "v_slots": [], + "validate_on_blur": null, + "value": null, + "vertical": null + } + }, + "aecb01c82b194b1da17292f2b99edd2a": { + "model_module": "jupyter-vuetify", + "model_name": "SelectModel", + "model_module_version": "^1.8.5", + "state": { + "_dom_classes": [], + "_events": [], + "_jupyter_vue": "IPY_MODEL_eb66bc8bbfb843b5aa6e3b001141d465", + "_metadata": null, + "_model_module": "jupyter-vuetify", + "_model_module_version": "^1.8.5", + "_model_name": "SelectModel", + "_view_count": null, + "_view_module": "jupyter-vuetify", + "_view_module_version": "^1.8.5", + "_view_name": "VuetifyView", + "append_icon": null, + "append_outer_icon": null, + "attach": null, + "attributes": {}, + "autofocus": null, + "background_color": null, + "cache_items": null, + "children": [], + "chips": null, + "class_": "", + "clear_icon": null, + "clearable": null, + "color": null, + "counter": null, + "dark": null, + "deletable_chips": null, + "dense": false, + "disable_lookup": null, + "disabled": false, + "eager": null, + "error": null, + "error_count": null, + "error_messages": null, + "filled": null, + "flat": null, + "full_width": null, + "height": null, + "hide_details": null, + "hide_selected": null, + "hint": null, + "id": null, + "item_color": null, + "item_disabled": null, + "item_text": null, + "item_value": null, + "items": [ + "types", + "random" + ], + "label": "label", + "layout": null, + "light": null, + "loader_height": null, + "loading": null, + "menu_props": null, + "messages": null, + "multiple": null, + "no_data_text": null, + "open_on_clear": null, + "outlined": null, + "persistent_hint": null, + "placeholder": null, + "prefix": null, + "prepend_icon": null, + "prepend_inner_icon": null, + "readonly": null, + "return_object": null, + "reverse": null, + "rounded": null, + "rules": null, + "shaped": null, + "single_line": null, + "slot": null, + "small_chips": null, + "solo": null, + "solo_inverted": null, + "style_": "", + "success": null, + "success_messages": null, + "suffix": null, + "type": null, + "v_model": "types", + "v_on": null, + "v_slots": [], + "validate_on_blur": null, + "value": null + } + }, + "16089329a19c46048f08cf23c8914a0f": { + "model_module": "jupyter-vuetify", + "model_name": "SheetModel", + "model_module_version": "^1.8.5", + "state": { + "_dom_classes": [], + "_events": [], + "_jupyter_vue": "IPY_MODEL_eb66bc8bbfb843b5aa6e3b001141d465", + "_metadata": null, + "_model_module": "jupyter-vuetify", + "_model_module_version": "^1.8.5", + "_model_name": "SheetModel", + "_view_count": null, + "_view_module": "jupyter-vuetify", + "_view_module_version": "^1.8.5", + "_view_name": "VuetifyView", + "attributes": {}, + "children": [ + "IPY_MODEL_f4d0360f79fb4982b7c17bfd8c25628e", + "IPY_MODEL_feec24aa04404492a8f1ad83ccfdd0ea", + "IPY_MODEL_1ebceeba0bac4a13b5938efae0a89280", + "IPY_MODEL_f44fe04cba424e348aef1aea8554378e" + ], + "class_": "d-flex ma-0", + "color": null, + "dark": null, + "elevation": 0, + "height": null, + "layout": null, + "light": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "slot": null, + "style_": "flex-direction: row; align-items: stretch; justify-content: start; column-gap: 12px;;", + "tag": null, + "tile": null, + "v_model": "!!disabled!!", + "v_on": null, + "v_slots": [], + "width": null + } + }, + "9b6d641d924647f08fcabf5d6685369d": { + "model_module": "jupyter-vuetify", + "model_name": "HtmlModel", + "model_module_version": "^1.8.10", + "state": { + "_dom_classes": [], + "_events": [], + "_jupyter_vue": "IPY_MODEL_eb66bc8bbfb843b5aa6e3b001141d465", + "_model_module": "jupyter-vuetify", + "_model_module_version": "^1.8.10", + "_model_name": "HtmlModel", + "_view_count": null, + "_view_module": "jupyter-vuetify", + "_view_module_version": "^1.8.10", + "_view_name": "VuetifyView", + "attributes": {}, + "children": [ + "IPY_MODEL_a62668efd08a4bae8183fb2b75c8c252", + "IPY_MODEL_a2a12ca90e1c48b696f0383411b1a50e" + ], + "class_": "", + "layout": null, + "slot": null, + "style_": "display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); grid-column-gap: 10px; grid-row-gap: 10px; align-items: stretch; justify-items: stretch", + "tag": "div", + "v_model": "!!disabled!!", + "v_on": null, + "v_slots": [] + } + }, + "f217f28e4dde401da22029e41661caf7": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "model_module_version": "1.2.0", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "489233570c5b418b99243d088f1a3ca7": { + "model_module": "@jupyter-widgets/base", + "model_name": "LayoutModel", + "model_module_version": "1.2.0", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + } + } + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Risky food simulation" + ], + "metadata": { + "id": "tNeScd2t_beT" + } + }, + { + "cell_type": "markdown", + "source": [ + "## setup\n", + "\n", + "install dependencies" + ], + "metadata": { + "id": "PAjn7zZ__d55" + } + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "D-I9oQ1ct-PI" + }, + "outputs": [], + "source": [ + "# install forked version of mesa for now, to get local enhancements for jupyterviz\n", + "%%capture\n", + "%pip uninstall --no-input mesa\n", + "%pip install git+https://github.com/Princeton-CDH/mesa.git@expand-jupyterviz#egg=mesa\n", + "#%pip install git+https://github.com/Princeton-CDH/mesa.git@583f20beb7efb15b15758573555ce7e74f3c8333#egg=mesa\n", + "\n", + "# install simulating risk code from github\n", + "%pip install git+https://github.com/Princeton-CDH/simulating-risk.git@main#egg=simulatingrisk" + ] + }, + { + "cell_type": "markdown", + "source": [ + "## run the simulation\n" + ], + "metadata": { + "id": "NojJqcFC_oXs" + } + }, + { + "cell_type": "code", + "source": [ + "from simulatingrisk.risky_food.app import page\n", + "\n", + "\n", + "page" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 686, + "referenced_widgets": [ + "55b570acd2ef4d97af85b752a4b7dcec", + "0e82d3840b7048c38d79f7773fb50313", + "4653195536cc47ed99889ab807e2a23a", + "a1a96e523827460da8558ea4d45acf2c", + "2d2b516e41f048ab8abbdf64eeba3154", + "b5c7e942f6cb4452ab7be0de14f84de3", + "d71a01b3f13a44aea387f635ce9e4fec", + "8568f5d71b3d4ff788789e1795825b55", + "8e1baf7101444819a0f8330008fac154", + "ad8e7e2c01ab43d39ea018054fd19583", + "f4d0360f79fb4982b7c17bfd8c25628e", + "feec24aa04404492a8f1ad83ccfdd0ea", + "1ebceeba0bac4a13b5938efae0a89280", + "f44fe04cba424e348aef1aea8554378e", + "a62668efd08a4bae8183fb2b75c8c252", + "a2a12ca90e1c48b696f0383411b1a50e", + "fa3ed8c4ed1249e7813920f66704105a", + "1206cdd010d64dc299fe94af4522348d", + "aecb01c82b194b1da17292f2b99edd2a", + "16089329a19c46048f08cf23c8914a0f", + "9b6d641d924647f08fcabf5d6685369d", + "f217f28e4dde401da22029e41661caf7", + "489233570c5b418b99243d088f1a3ca7" + ] + }, + "id": "auDMmFlQ_YYQ", + "outputId": "9a426c66-0564-4aa5-fb55-68570ce4e310" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "Html(layout=None, style_='display: none', tag='span')" + ], + "application/vnd.jupyter.widget-view+json": { + "version_major": 2, + "version_minor": 0, + "model_id": "55b570acd2ef4d97af85b752a4b7dcec" + } + }, + "metadata": { + "application/vnd.jupyter.widget-view+json": { + "colab": { + "custom_widget_manager": { + "url": "https://ssl.gstatic.com/colaboratory-static/widgets/colab-cdn-widget-manager/b3e629b1971e1542/manager.min.js" + } + } + } + } + }, + { + "output_type": "display_data", + "data": { + "text/plain": [ + "Cannot show ipywidgets in text" + ], + "text/html": [ + "Cannot show widget. You probably want to rerun the code cell above (Click in the code cell, and press Shift+Enter +)." + ], + "application/vnd.jupyter.widget-view+json": { + "version_major": 2, + "version_minor": 0, + "model_id": "8568f5d71b3d4ff788789e1795825b55" + } + }, + "metadata": { + "application/vnd.jupyter.widget-view+json": { + "colab": { + "custom_widget_manager": { + "url": "https://ssl.gstatic.com/colaboratory-static/widgets/colab-cdn-widget-manager/b3e629b1971e1542/manager.min.js" + } + } + } + } + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "## data analysis\n", + "\n", + "Run preliminary data analysis on data collected by the model.\n", + "\n", + "**NOTE**: Re-run this step after re-running the simulation to get the latest data" + ], + "metadata": { + "id": "s__PSHCH_xiL" + } + }, + { + "cell_type": "code", + "source": [ + "# get model data from the data collector\n", + "# page.args[0] == viz object;\n", + "model_df = page.args[0].model.datacollector.get_model_vars_dataframe()\n", + "# convert boolean contaminated flag to 0/1 so we can more easily plot\n", + "model_df[\"contaminated_i\"] = model_df[\"contaminated\"].astype(int)\n", + "model_df.head(10)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 383 + }, + "id": "zWhAdA-g2YO8", + "outputId": "561a9909-d9ed-4819-ad90-98b74b641cf3" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + " prob_notcontaminated contaminated average_risk_level min_risk_level \\\n", + "0 0.564714 0 0.500000 0.0 \n", + "1 0.319115 0 0.411765 0.0 \n", + "2 0.091997 1 0.328000 0.0 \n", + "3 0.172058 1 0.356522 0.0 \n", + "4 0.195650 0 0.400000 0.0 \n", + "5 0.519607 1 0.356522 0.0 \n", + "6 0.359001 1 0.435714 0.0 \n", + "7 0.636125 0 0.523810 0.0 \n", + "8 0.407315 1 0.447059 0.0 \n", + "9 0.709399 0 0.528000 0.0 \n", + "\n", + " max_risk_level num_agents contaminated_i \n", + "0 1.0 110 0 \n", + "1 1.0 170 0 \n", + "2 1.0 250 1 \n", + "3 1.0 230 1 \n", + "4 1.0 200 0 \n", + "5 1.0 230 1 \n", + "6 1.0 140 1 \n", + "7 1.0 105 0 \n", + "8 1.0 170 1 \n", + "9 1.0 125 0 " + ], + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
prob_notcontaminatedcontaminatedaverage_risk_levelmin_risk_levelmax_risk_levelnum_agentscontaminated_i
00.56471400.5000000.01.01100
10.31911500.4117650.01.01700
20.09199710.3280000.01.02501
30.17205810.3565220.01.02301
40.19565000.4000000.01.02000
50.51960710.3565220.01.02301
60.35900110.4357140.01.01401
70.63612500.5238100.01.01050
80.40731510.4470590.01.01701
90.70939900.5280000.01.01250
\n", + "
\n", + "
\n", + "\n", + "
\n", + " \n", + "\n", + " \n", + "\n", + " \n", + "
\n", + "\n", + "\n", + "
\n", + " \n", + "\n", + "\n", + "\n", + " \n", + "
\n", + "
\n", + "
\n" + ] + }, + "metadata": {}, + "execution_count": 3 + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "### state of the world for this run\n", + "\n", + "- what was the probability that the food would _NOT_ be contaminated? (blue trendline)\n", + "- when was the food actually contaminated? (red bar indicates contamination)" + ], + "metadata": { + "id": "AsbyUc_P_8gi" + } + }, + { + "cell_type": "code", + "source": [ + "import altair as alt\n", + "\n", + "base = alt.Chart(model_df.reset_index()).mark_line().encode(\n", + " x='index', # alt.X('index').title(\"round\"),\n", + " y='prob_notcontaminated',\n", + ").properties(\n", + " width=800,\n", + " height=200\n", + ")\n", + "\n", + "actual_n = base.mark_bar(color=\"red\", opacity=0.7, width=3).encode(\n", + " # x=alt.X('index').title(\"round\"),\n", + " y='contaminated_i')\n", + "# combine the two charts vertically\n", + "alt.vconcat(base, actual_n.properties(width=800,height=50))" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 410 + }, + "id": "VLPQXTtA4zGK", + "outputId": "58deffbc-2b9d-4dec-f529-6c31cab57b3a" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/html": [ + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.VConcatChart(...)" + ] + }, + "metadata": {}, + "execution_count": 4 + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "### agent risk attitudes" + ], + "metadata": { + "id": "CyqDiun_ATjs" + } + }, + { + "cell_type": "code", + "source": [ + "# get agent data from model collected data\n", + "agent_df = page.args[0].model.datacollector.get_agent_vars_dataframe()\n", + "agent_df = agent_df.reset_index() # reset index so we can access by step\n", + "agent_df" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 424 + }, + "id": "qiwnsSa_5Tk-", + "outputId": "bc727f33-02ff-4b32-b1b4-6b92d1612f8f" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + " Step AgentID risk_level payoff\n", + "0 1 0-0 0.0 4\n", + "1 1 0-1 0.0 4\n", + "2 1 0-2 0.0 4\n", + "3 1 0-3 0.0 4\n", + "4 1 0-4 0.0 4\n", + "... ... ... ... ...\n", + "12140792 62 2232769 0.1 1\n", + "12140793 62 2232770 0.1 1\n", + "12140794 62 2232771 0.1 1\n", + "12140795 62 2232772 0.1 1\n", + "12140796 62 2232773 0.1 1\n", + "\n", + "[12140797 rows x 4 columns]" + ], + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
StepAgentIDrisk_levelpayoff
010-00.04
110-10.04
210-20.04
310-30.04
410-40.04
...............
121407926222327690.11
121407936222327700.11
121407946222327710.11
121407956222327720.11
121407966222327730.11
\n", + "

12140797 rows × 4 columns

\n", + "
\n", + "
\n", + "\n", + "
\n", + " \n", + "\n", + " \n", + "\n", + " \n", + "
\n", + "\n", + "\n", + "
\n", + " \n", + "\n", + "\n", + "\n", + " \n", + "
\n", + "
\n", + "
\n" + ] + }, + "metadata": {}, + "execution_count": 5 + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "#### last round\n", + "\n", + "what is the state of things at the last round run?" + ], + "metadata": { + "id": "EJqnV949AbvD" + } + }, + { + "cell_type": "code", + "source": [ + "# get data for the last round\n", + "last_step_n = max(agent_df.Step)\n", + "last_step = agent_df[agent_df.Step == last_step_n]\n", + "last_step.head(10)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 363 + }, + "id": "2XG0t9q76igQ", + "outputId": "f48270e0-968a-4305-e064-431fcb03d87b" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + " Step AgentID risk_level payoff\n", + "9989907 62 1-0 0.1 1\n", + "9989908 62 1-1 0.1 1\n", + "9989909 62 1-2 0.1 1\n", + "9989910 62 1-3 0.1 1\n", + "9989911 62 1-4 0.1 1\n", + "9989912 62 2-0 0.2 1\n", + "9989913 62 2-1 0.2 1\n", + "9989914 62 2-2 0.2 1\n", + "9989915 62 2-3 0.2 1\n", + "9989916 62 2-4 0.2 1" + ], + "text/html": [ + "\n", + "
\n", + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
StepAgentIDrisk_levelpayoff
9989907621-00.11
9989908621-10.11
9989909621-20.11
9989910621-30.11
9989911621-40.11
9989912622-00.21
9989913622-10.21
9989914622-20.21
9989915622-30.21
9989916622-40.21
\n", + "
\n", + "
\n", + "\n", + "
\n", + " \n", + "\n", + " \n", + "\n", + " \n", + "
\n", + "\n", + "\n", + "
\n", + " \n", + "\n", + "\n", + "\n", + " \n", + "
\n", + "
\n", + "
\n" + ] + }, + "metadata": {}, + "execution_count": 6 + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "What's the risk attitude distribution on the last round?" + ], + "metadata": { + "id": "YnKMFcY_AlxN" + } + }, + { + "cell_type": "code", + "source": [ + "# describe risk level parameter\n", + "last_step.risk_level.describe()\n" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Esqx5Vxe6usE", + "outputId": "d1528827-f673-41f3-fddb-a0e1c8600e28" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "count 2.150890e+06\n", + "mean 5.544691e-01\n", + "std 6.644578e-02\n", + "min 1.000000e-01\n", + "25% 5.000000e-01\n", + "50% 6.000000e-01\n", + "75% 6.000000e-01\n", + "max 1.000000e+00\n", + "Name: risk_level, dtype: float64" + ] + }, + "metadata": {}, + "execution_count": 7 + } + ] + }, + { + "cell_type": "code", + "source": [ + "# plot a histogram of risk levels on the last round\n", + "import matplotlib.pylab as plt\n", + "%matplotlib inline\n", + "\n", + "last_step.risk_level.hist(range=[0,1], bins=11)\n", + "plt.show()" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 445 + }, + "id": "PBp8lynu639g", + "outputId": "a8bc828a-29db-47dd-c60e-d505dcac34e0" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "
" + ], + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAiMAAAGsCAYAAAAPJKchAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAm2ElEQVR4nO3df1BV953/8RcgXGQjMZYIyJISzRqT+ovCwhLrJO4XpNah6+x068Sssmw0mwo71jtpIvEHUBuxVl26XRImJsZmNgZjJzFtZVRKS91EGkeU2aRRs0YNXQ2o6yYQaC9X7vn+4Xizt/yQc733fnovz8eMw9wPn/M57/vmcHl5zrkQZVmWJQAAAEOiTRcAAABGN8IIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMCqswsjhw4dVVFSkSZMmKSoqSvv27bO9hmVZ2rp1q6ZOnSqHw6G0tDQ988wzgS8WAACMyBjTBdjR09OjWbNm6R//8R/1t3/7t36tsWrVKh06dEhbt27VjBkzdPXqVV29ejXAlQIAgJGKCtc/lBcVFaU33nhDixYt8o65XC6tXbtWr776qj755BNNnz5d3//+9/XQQw9Jkk6ePKmZM2fqvffe07333mumcAAA4COsLtPcTFlZmVpaWlRfX6///M//1N/93d/pq1/9qv7rv/5LkvSzn/1MkydP1s9//nPdfffdysjI0PLlyzkzAgCAQRETRtrb2/XSSy9p7969mjt3rqZMmaInnnhCX/nKV/TSSy9Jks6ePauPPvpIe/fu1csvv6xdu3aptbVV3/jGNwxXDwDA6BVW94wM591331V/f7+mTp3qM+5yufSFL3xBkuTxeORyufTyyy9757344ovKysrS6dOnuXQDAIABERNGPvvsM8XExKi1tVUxMTE+n7vtttskSampqRozZoxPYLnvvvskXT+zQhgBACD0IiaMZGZmqr+/X5cuXdLcuXMHnTNnzhxdu3ZNH374oaZMmSJJ+uCDDyRJX/ziF0NWKwAA+FxYvZvms88+05kzZyRdDx/bt2/XvHnzNGHCBN111136+7//e7399tvatm2bMjMzdfnyZTU1NWnmzJlauHChPB6P/vIv/1K33Xabampq5PF4VFpaqsTERB06dMjwswMAYHQKqzDS3NysefPmDRgv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LZcnyq813qvkMvXNcDyHRjD7fOPKxs0EPYykpKSos7PTZ6yzs1OJiYmDnhWRJIfDIYfDMWA8NjY2KAdksNaFL/ocOqO9165+/wKC7f14ovze12j++tg12o/nUAlGn0e6XtB/z0heXp6ampp8xhobG5WXlxfsXQMAgDBgO4x89tlnamtrU1tbm6Trb91ta2tTe3u7pOuXWJYtW+ad//jjj+vs2bN68sknderUKT377LN67bXXtHr16sA8AwAAENZsh5Fjx44pMzNTmZmZkiSn06nMzExt2LBBkvTxxx97g4kk3X333dq/f78aGxs1a9Ysbdu2TS+88AJv6wUAAJL8uGfkoYcekmUN/Xa2wX676kMPPaQTJ07Y3RUAABgF+Ns0AADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADDKrzBSW1urjIwMxcfHKzc3V0ePHh12fk1Nje69916NHTtW6enpWr16tf7whz/4VTAAAIgstsPInj175HQ6VVFRoePHj2vWrFkqLCzUpUuXBp2/e/durVmzRhUVFTp58qRefPFF7dmzR08//fQtFw8AAMKf7TCyfft2rVixQiUlJbr//vtVV1enhIQE7dy5c9D5R44c0Zw5c7RkyRJlZGRo/vz5evjhh296NgUAAIwOY+xM7uvrU2trq8rLy71j0dHRys/PV0tLy6DbPPDAA/r3f/93HT16VDk5OTp79qwaGhq0dOnSIffjcrnkcrm8j7u6uiRJbrdbbrfbTsnDurFWINfEQPQ5dOj1dY4YK7jrR1s+H/0x2r9GI8HxHBrB7PNI14yyLGvE300XL15UWlqajhw5ory8PO/4k08+qV//+td65513Bt3uX//1X/XEE0/Isixdu3ZNjz/+uJ577rkh91NZWamqqqoB47t371ZCQsJIywUA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ijACAACMIowAAACjCCMAAMAov8JIbW2tMjIyFB8fr9zcXB09enRE29XX1ysqKkqLFi3yZ7cAACAC2Q4je/bskdPpVEVFhY4fP65Zs2apsLBQly5dGna78+fP64knntDcuXP9LhYAAEQe22Fk+/btWrFihUpKSnT//ferrq5OCQkJ2rlz55Db9Pf365FHHlFVVZUmT558SwUDAIDIMsbO5L6+PrW2tqq8vNw7Fh0drfz8fLW0tAy53Xe/+11NnDhRjz76qP7jP/7jpvtxuVxyuVzex11dXZIkt9stt9ttp+Rh3VgrkGtiIPocOqHotSPGCtra4cIRbfl89AffDzfHa0doBLPPI13TVhi5cuWK+vv7lZyc7DOenJysU6dODbrNW2+9pRdffFFtbW0j3k91dbWqqqoGjB86dEgJCQl2Sh6RxsbGgK+Jgehz6ASz11tygrZ02NmY7fF724aGhgBWEtl47QiNYPS5t7d3RPNshRG7uru7tXTpUu3YsUNJSUkj3q68vFxOp9P7uKurS+np6Zo/f74SExMDVp/b7VZjY6MKCgoUGxsbsHXhiz6HTih6Pb3yYFDWDSeOaEsbsz1afyxaLk+UX2u8V1kY4KoiD68doRHMPt+4snEztsJIUlKSYmJi1NnZ6TPe2dmplJSUAfM//PBDnT9/XkVFRd4xj+f6/yTGjBmj06dPa8qUKQO2czgccjgcA8ZjY2ODckAGa134os+hE8xeu/r9++EbiVyeKL/7wffCyPHaERrB6PNI17N1A2tcXJyysrLU1NTkHfN4PGpqalJeXt6A+dOmTdO7776rtrY277+vf/3rmjdvntra2pSenm5n9wAAIALZvkzjdDpVXFys7Oxs5eTkqKamRj09PSopKZEkLVu2TGlpaaqurlZ8fLymT5/us/348eMlacA4AAAYnWyHkcWLF+vy5cvasGGDOjo6NHv2bB04cMB7U2t7e7uio/nFrgAAYGT8uoG1rKxMZWVlg36uubl52G137drlzy4BAECE4hQGAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgjAADAKMIIAAAwijACAACMIowAAACjCCMAAMAowggAADCKMAIAAIwijAAAAKMIIwAAwCjCCAAAMIowAgAAjCKMAAAAowgj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\n" + }, + "metadata": {} + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "#### multiple rounds\n", + "\n", + "plot agent risk levels across rounds periodically" + ], + "metadata": { + "id": "gGRTfU0CA0Z7" + } + }, + { + "cell_type": "code", + "source": [ + "import matplotlib.pyplot as plt\n", + "# create a grid to plot multiple rounds\n", + "fig, ax = plt.subplots(ncols=4, nrows=4, sharex='col', sharey='row', figsize=(18,10))\n", + "\n", + "# try plotting every 5 rounds\n", + "# (harder to run this one as long due to population expansion)\n", + "max_plots = 4 * 4\n", + "\n", + "# iterate by fives starting with 5\n", + "for i, round in enumerate(range(5, last_step_n, 5)):\n", + " if i >= max_plots: # don't go beyond what our subplot grid can handle\n", + " break\n", + " round_data = agent_df[agent_df.Step == round]\n", + " plot_location = ax[int(i/4), int(i % 4)]\n", + " round_data.risk_level.hist(ax=plot_location, range=[0,1], bins=11)\n", + " plot_location.set_title(\"round %d\" % (round,))\n" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 454 + }, + "id": "rlCE6_dm7kvX", + "outputId": "eee0538e-3849-466d-a65a-6e33f3655e26" + }, + "execution_count": null, + "outputs": [ + { + "output_type": "display_data", + "data": { + "text/plain": [ + "
" + ], + "image/png": "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7yotLU02m02fffaZkpOT3X+e88gjj+jUqVMe27pcLs2aNUvXXnutQkND1adPHx05csSrTFFRUR4N8dI5DhgwQA6HQ1999VW52/3444+6cOGCV/sAgJpmxnpdkdatW0v65c85S61du1Z16tRRQkKCe1lISIhGjhyprKwsffvtt1e0TwC4Wqxery/G8TWA2szM9drpdOq5557Tc889pzZt2pQ7Zu3atQoLC9Ojjz7qXta8eXM99thj+uCDD8o9uxzmRlMcljR48GAVFhZq9uzZGj16tCRp1KhRmjp1qm677TYtXLhQd999t+bMmaMhQ4Zc0b7Gjh2rQ4cOadq0aRozZow+/PBDJSUleYyZOnWqpkyZos6dO+vll1/W9ddfr9jYWJ07d87n/ebm5kqSmjVrVmbdiBEjZLfbFRISoj59+pR7rS0AqA3MWK8vXLig77//XidOnFBGRoYmT56sBg0aqHv37u4xBw4c0I033ii73e6xbemYgwcP+h4UAKqBVet1KY6vARiFGev1okWLdObMGU2ePLnCMQcOHNBtt91W5vJX3bt3V2Fhob744ouqhYPhcfkUWFLnzp09/jTm0KFDWrlypUaNGqU//vGPkn65BEmLFi00f/58ffLJJ+rTp49P+2ratKkyMjJks9kk/XIdq8WLF+vs2bNq2LChTp06pdTUVMXHx+vDDz90j/vd736n2bNn+7TP06dP680331SvXr3UsmVL9/KgoCANHDhQ/fr1U7NmzZSTk6P58+erV69e2rlzp2699Vaf9gcA1cWM9Xrfvn2Kjo5237/pppu0bt06NWnSxL3su+++86jfpUqXnThxwqeMAFBdrFqvOb4GYDRmq9e5ubmaOXOm5s+fX+aEkot999136t27d5nlFx9fd+zYsaoRYWCcKQ5LevbZZz3ub9y4UZKUnJzssfy3v/2tJGnDhg0+7yshIcFd2KVfrjdYXFysb775RpL08ccfq6ioSGPHjvUYN27cOJ/2V1JSomHDhik/P19LlizxWHfHHXdo7dq1euaZZ/Twww/rxRdf1K5du2Sz2TRp0iSf9gcA1cmM9bp9+/bKzMzU+++/7/7eh59++sljzM8//6zg4OAy24aEhLjXA0BtYtV6zfE1AKMxW71+4YUXdP3112vUqFGXHcfxNS7FmeKwpKioKI/733zzjQICAsp8Q3x4eLgaNWrk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\n" + }, + "metadata": {} + } + ] + } + ] +} \ No newline at end of file From 414adc70f16d4491c6b2cc8513535b87bb2f402a Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 5 Sep 2023 13:24:17 -0400 Subject: [PATCH 067/141] Add readme for notebooks folder --- notebooks/README.md | 19 +++++++++++++++++++ ...is.ipynb => riskybet_batch_analysis.ipynb} | 0 2 files changed, 19 insertions(+) create mode 100644 notebooks/README.md rename notebooks/{risky_bet_batch_analysis.ipynb => riskybet_batch_analysis.ipynb} (100%) diff --git a/notebooks/README.md b/notebooks/README.md new file mode 100644 index 0000000..cf09567 --- /dev/null +++ b/notebooks/README.md @@ -0,0 +1,19 @@ +# Simulating Risk notebooks + +This folder includes Jupyter/Colab notebooks associated with the +Simulating Risk project. Some notebooks allow running the simulations +directly within a notebook; others are for analysis of simulation results. + +## Risky Bet + +Refer to [game description](../simulatingrisk/risky_bet) for details. + +* [simulation](riskybet_simulation.ipynb) +* [batch analysis analysis](riskybet_batch_analysis.ipynb) + +## Risky Food + +Refer to [game description](../simulatingrisk/risky_food) for details. + +* [simulation](riskyfood_simulation.ipynb) +* [batch analysis analysis](riskyfood_batch_analysis.ipynb) diff --git a/notebooks/risky_bet_batch_analysis.ipynb b/notebooks/riskybet_batch_analysis.ipynb similarity index 100% rename from notebooks/risky_bet_batch_analysis.ipynb rename to notebooks/riskybet_batch_analysis.ipynb From 4f73540806b9098695ea4d13b18428ecedbb6b34 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 6 Sep 2023 16:31:42 -0400 Subject: [PATCH 068/141] Implement custom agent space drawer with altair for hawk/dove game --- simulatingrisk/hawkdove/app.py | 7 +++- simulatingrisk/hawkdove/server.py | 57 +++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/simulatingrisk/hawkdove/app.py b/simulatingrisk/hawkdove/app.py index 0c22be7..b0eea3b 100644 --- a/simulatingrisk/hawkdove/app.py +++ b/simulatingrisk/hawkdove/app.py @@ -2,7 +2,11 @@ from mesa.experimental import JupyterViz from simulatingrisk.hawkdove.model import HawkDoveModel -from simulatingrisk.hawkdove.server import agent_portrayal, jupyterviz_params +from simulatingrisk.hawkdove.server import ( + agent_portrayal, + jupyterviz_params, + draw_hawkdove_agent_space, +) page = JupyterViz( @@ -12,6 +16,7 @@ # measures=[plot_risk_histogram], name="Hawk/Dove with risk attitudes", agent_portrayal=agent_portrayal, + space_drawer=draw_hawkdove_agent_space, ) # required to render the visualization with Jupyter/Solara page diff --git a/simulatingrisk/hawkdove/server.py b/simulatingrisk/hawkdove/server.py index 6b21163..6c3cd53 100644 --- a/simulatingrisk/hawkdove/server.py +++ b/simulatingrisk/hawkdove/server.py @@ -2,6 +2,10 @@ Configure visualization elements and instantiate a server """ +import altair as alt +import solara +import pandas as pd + from simulatingrisk.hawkdove.model import Play @@ -17,9 +21,9 @@ def agent_portrayal(agent): "Layer": 0, "r": 0.2, "risk_level": agent.risk_level, - "choice": str(agent.choice) + "choice": str(agent.choice), # styles for solara / jupyterviz - # "size": 25, + "size": 25, # "color": "tab:gray", } @@ -35,6 +39,7 @@ def agent_portrayal(agent): # filled for hawks, hollow for doves # (shapes would be better...) portrayal["Filled"] = agent.choice == Play.HAWK + portrayal["choice"] = "hawk" if agent.choice == Play.HAWK else "dove" # size based on points within current distribution after first round if agent.points > 0: @@ -70,3 +75,51 @@ def agent_portrayal(agent): # "description": "How agents update their risk level", # }, } + + +def draw_hawkdove_agent_space(model, agent_portrayal): + # custom agent space chart, modeled on default + # make_space method in mesa jupyterviz code, + # but using altair so we can contrl shapes as well as color and size + all_agent_data = [] + for i in range(model.grid.width): + for j in range(model.grid.height): + agent_data = {} + content = model.grid._grid[i][j] + if not content: + continue + if not hasattr(content, "__iter__"): + # Is a single grid + content = [content] + for agent in content: + # use all data from agent portrayal, and add x,y coordinates + agent_data = agent_portrayal(agent) + agent_data["x"] = i + agent_data["y"] = j + all_agent_data.append(agent_data) + + df = pd.DataFrame(all_agent_data) + + # use grid x,y coordinates to plot, but supress axis labels + + # currently passing in actual colors, not a variable to use for color + # use domain/range to use color for display + colors = list(set(a["color"] for a in all_agent_data)) + shape_domains = ("hawk", "dove") + shape_range = ("triangle-up", "circle") + + chart = ( + alt.Chart(df) + .mark_point(filled=True) + .encode( + x=alt.X("x", axis=None), # no x-axis label + y=alt.Y("y", axis=None), # no y-axis label + size=alt.Size("size", title="points rank"), # relabel size for legend + color=alt.Color("color").legend(None).scale(domain=colors, range=colors), + shape=alt.Shape( # use shape to indicate choice + "choice", scale=alt.Scale(domain=shape_domains, range=shape_range) + ), + ) + .configure_view(strokeOpacity=0) # hide grid/chart lines + ) + return solara.FigureAltair(chart) From 4d7966bdda4174a0f2930274a3aeb72e88ab6e15 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 6 Sep 2023 17:04:34 -0400 Subject: [PATCH 069/141] Implement option to initialize all hawk/dove with the same risk level --- simulatingrisk/hawkdove/model.py | 21 ++++++++++++++---- simulatingrisk/hawkdove/server.py | 36 +++++++++++++++++++++++++++---- tests/test_hawkdove.py | 21 ++++++++++++++++++ 3 files changed, 70 insertions(+), 8 deletions(-) diff --git a/simulatingrisk/hawkdove/model.py b/simulatingrisk/hawkdove/model.py index 123f72e..9ee0219 100644 --- a/simulatingrisk/hawkdove/model.py +++ b/simulatingrisk/hawkdove/model.py @@ -33,7 +33,7 @@ class HawkDoveAgent(mesa.Agent): An agent with a risk attitude playing Hawk or Dove """ - def __init__(self, unique_id, model): + def __init__(self, unique_id, model, risk_level=None): super().__init__(unique_id, model) self.points = 0 @@ -44,7 +44,7 @@ def __init__(self, unique_id, model): # - based partially on neighborhood size, # which is configurable at the model level num_neighbors = 8 if self.model.include_diagonals else 4 - self.risk_level = self.random.randint(0, num_neighbors) + self.risk_level = risk_level or self.random.randint(0, num_neighbors) def initial_choice(self): # first round : choose what to play randomly or based on initial setup @@ -118,21 +118,34 @@ class HawkDoveModel(mesa.Model): running = True # required for batch run - def __init__(self, grid_size, include_diagonals=True): + def __init__( + self, + grid_size, + include_diagonals=True, + risk_attitudes="variable", + agent_risk_level=None, + ): super().__init__() # assume a fully-populated square grid self.num_agents = grid_size * grid_size # mesa get_neighbors supports moore neighborhood (include diagonals) # and von neumann (exclude diagonals) self.include_diagonals = include_diagonals + self.risk_attitudes = risk_attitudes # initialize a single grid (each square inhabited by a single agent); # configure the grid to wrap around so everyone has neighbors self.grid = mesa.space.SingleGrid(grid_size, grid_size, True) self.schedule = mesa.time.StagedActivation(self, ["choose", "play"]) + agent_opts = {} + # when started in single risk attitude mode, initialize all agents + # with the specified risk level + if risk_attitudes == "single" and agent_risk_level: + agent_opts["risk_level"] = agent_risk_level + for i in range(self.num_agents): - agent = HawkDoveAgent(i, self) + agent = HawkDoveAgent(i, self, **agent_opts) self.schedule.add(agent) # place randomly in an empty spot self.grid.move_to_empty(agent) diff --git a/simulatingrisk/hawkdove/server.py b/simulatingrisk/hawkdove/server.py index 6c3cd53..3d72f9e 100644 --- a/simulatingrisk/hawkdove/server.py +++ b/simulatingrisk/hawkdove/server.py @@ -32,6 +32,7 @@ def agent_portrayal(agent): colors = divergent_colors_9 else: colors = divergent_colors_5 + portrayal["Color"] = colors[agent.risk_level] # copy to lowercase color for solara portrayal["color"] = portrayal["Color"] @@ -68,6 +69,18 @@ def agent_portrayal(agent): "max": 100, "step": 1, }, + "include_diagonals": { + "type": "Checkbox", + "value": True, + "label": "Include diagonal neighbors", + }, + "risk_attitudes": { + "type": "Select", + "value": "variable", + "values": ["variable", "single"], + "description": "Agent initial risk level", + }, + "agent_risk_level": {"type": "SliderInt", "min": 0, "max": 8, "step": 1, "value": 2} # "risk_adjustment": { # "type": "Select", # "value": "adopt", @@ -99,15 +112,30 @@ def draw_hawkdove_agent_space(model, agent_portrayal): all_agent_data.append(agent_data) df = pd.DataFrame(all_agent_data) + # print(all_agent_data) # use grid x,y coordinates to plot, but supress axis labels # currently passing in actual colors, not a variable to use for color # use domain/range to use color for display - colors = list(set(a["color"] for a in all_agent_data)) - shape_domains = ("hawk", "dove") + hawkdove_domain = ("hawk", "dove") shape_range = ("triangle-up", "circle") + # FIXME: model doesn't get updated when the input changes; + # seems to only get a reference to the initial model + + # when risk attitude is variable, + # use divergent color scheme to indicate risk level + if model.risk_attitudes == "variable": + colors = list(set(a["color"] for a in all_agent_data)) + chart_color = alt.Color("color").legend(None).scale(domain=colors, range=colors) + elif model.risk_attitudes == "single": + chart_color = ( + alt.Color("choice") + # .legend(None) + .scale(domain=hawkdove_domain, range=["orange", "blue"]) + ) + chart = ( alt.Chart(df) .mark_point(filled=True) @@ -115,9 +143,9 @@ def draw_hawkdove_agent_space(model, agent_portrayal): x=alt.X("x", axis=None), # no x-axis label y=alt.Y("y", axis=None), # no y-axis label size=alt.Size("size", title="points rank"), # relabel size for legend - color=alt.Color("color").legend(None).scale(domain=colors, range=colors), + color=chart_color, shape=alt.Shape( # use shape to indicate choice - "choice", scale=alt.Scale(domain=shape_domains, range=shape_range) + "choice", scale=alt.Scale(domain=hawkdove_domain, range=shape_range) ), ) .configure_view(strokeOpacity=0) # hide grid/chart lines diff --git a/tests/test_hawkdove.py b/tests/test_hawkdove.py index e5e7d09..23e1864 100644 --- a/tests/test_hawkdove.py +++ b/tests/test_hawkdove.py @@ -28,6 +28,27 @@ def test_agent_initial_choice(): assert math.isclose(total, half_agents, rel_tol=0.05) +def test_agent_initial_risk_level(): + agent = HawkDoveAgent(1, Mock(), risk_level=2) + assert agent.risk_level == 2 + + +def test_model_single_risk_level(): + risk_level = 3 + model = HawkDoveModel( + 5, include_diagonals=True, risk_attitudes="single", agent_risk_level=risk_level + ) + for agent in model.schedule.agents: + assert agent.risk_level == risk_level + + +def test_model_variable_risk_level(): + model = HawkDoveModel(5, include_diagonals=True, risk_attitudes="variable") + # when risk level is variable/random, agents should have different risk levels + risk_levels = set([agent.risk_level for agent in model.schedule.agents]) + assert len(risk_levels) > 1 + + def test_num_hawk_neighbors(): # initialize an agent with a mock model agent = HawkDoveAgent(1, Mock()) From f27373925d5ad3b692361ca44fffe0ed41542e4f Mon Sep 17 00:00:00 2001 From: Rebecca Sutton Koeser Date: Tue, 12 Sep 2023 15:13:57 -0400 Subject: [PATCH 070/141] Add a 0-1 risk level diagram to readme use mermaid.js diagram to make risk level definitions more clear --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 0a2a3aa..5140d47 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,25 @@ Simulations are implemented with [Mesa](https://mesa.readthedocs.io/en/stable/), Across simulations, we define agents with risk attitudes tracked via a numeric `r` or `risk_level` 0.0 - 1.0, where `r` is that agent's minimum acceptable risk level. An agent with `r=1` will always take the safe option (no risk is acceptable); an agent with `r=0` will always take the risky choice (any risk is acceptable). + + +```mermaid +--- +title: risk attitude / risk level (for probabilistic choices) +--- +flowchart LR + r0["0.0 +always takes risky choice +(any risk is acceptable)"] + reu["0.5 +expected utility"] + r1["1.0 +always takes safe choice +(no risk is acceptable)"] + r0 ---|risk seeking|reu---|risk averse|r1 +``` + + ## Development instructions Initial setup and installation: From 6f550e90c160be099712cbceaf75be2fc78e5356 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 12 Sep 2023 15:17:29 -0400 Subject: [PATCH 071/141] Update hawk/dove rules to document revised logic for choosing play Co-authored-by: Lara Buchak <140551577+LaraBuchak@users.noreply.github.com> --- simulatingrisk/hawkdove/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/simulatingrisk/hawkdove/README.md b/simulatingrisk/hawkdove/README.md index b723af2..d0fe99a 100644 --- a/simulatingrisk/hawkdove/README.md +++ b/simulatingrisk/hawkdove/README.md @@ -28,8 +28,7 @@ Players arranged on a lattice [try both 4 neighbors (AYBD) and 8 neighbors (XYZA Each player on a lattice (grid in Mesa): - Has parameter `r` [from 0 to 8, or 0 to 4 for four neighbors] -- Let `h` be the number of neighbors who played HAWK during the previous round. If `h` > `r`, then play DOVE. Otherwise play HAWK. - - [TODO: make sure comparison and risk attitude is defined consistently with other simulations] +- Let `d` be the number of neighbors who played DOVE during the previous round. If `d > r`, then play HAWK. Otherwise play DOVE. (Agents who are risk-avoidant only play HAWK if there are a lot of doves around them. More risk-avoidance requires a higher number of doves to get an agent to play HAWK.) - Choice for the first round could be randomly determined, or add parameters to see how initial conditions matter? - [OR VARY FIRST ROUND: what proportion starts as HAWK - [Who is a HAWK and who is a DOVE is randomly determined; proportion set at the beginning of each simulation. E.g. 30% are HAWKS; if we have 100 players, then each player has a 30% chance of being HAWK] From 275b4c722d4a9662ef80cf27b3eaf474577923e5 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 12 Sep 2023 15:18:13 -0400 Subject: [PATCH 072/141] Update hawk/dove choice logic to calculate based on number of doves --- simulatingrisk/hawkdove/model.py | 12 +++++++----- tests/test_hawkdove.py | 14 +++++++------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/simulatingrisk/hawkdove/model.py b/simulatingrisk/hawkdove/model.py index 9ee0219..8453889 100644 --- a/simulatingrisk/hawkdove/model.py +++ b/simulatingrisk/hawkdove/model.py @@ -59,9 +59,9 @@ def neighbors(self): ) @property - def num_hawk_neighbors(self): - """count how many neighbors played HAWK on the last round""" - return len([n for n in self.neighbors if n.last_choice == Play.HAWK]) + def num_dove_neighbors(self): + """count how many neighbors played DOVE on the last round""" + return len([n for n in self.neighbors if n.last_choice == Play.DOVE]) def choose(self): "decide what to play this round" @@ -70,12 +70,14 @@ def choose(self): # store previous choice self.last_choice = self.choice - # TODO: how to make risk attitude consistent with other sims? + # choose based on the number of neighbors who played + # dove last round and agent risk level + # agent with r = 0 should always take the risky choice # (any risk is acceptable). # agent with r = max should always take the safe option # (no risk is acceptable) - if self.risk_level < self.num_hawk_neighbors: + if self.num_dove_neighbors > self.risk_level: self.choice = Play.HAWK else: self.choice = Play.DOVE diff --git a/tests/test_hawkdove.py b/tests/test_hawkdove.py index 23e1864..33cab37 100644 --- a/tests/test_hawkdove.py +++ b/tests/test_hawkdove.py @@ -49,7 +49,7 @@ def test_model_variable_risk_level(): assert len(risk_levels) > 1 -def test_num_hawk_neighbors(): +def test_num_dove_neighbors(): # initialize an agent with a mock model agent = HawkDoveAgent(1, Mock()) mock_neighbors = [ @@ -60,7 +60,7 @@ def test_num_hawk_neighbors(): ] with patch.object(HawkDoveAgent, "neighbors", mock_neighbors): - assert agent.num_hawk_neighbors == 3 + assert agent.num_dove_neighbors == 1 def test_agent_choose(): @@ -73,21 +73,21 @@ def test_agent_choose(): # on subsequent rounds, choose based on neighbors and risk level agent.model.schedule.steps = 1 - # given a specified number of hawk neighbors and risk level - with patch.object(HawkDoveAgent, "num_hawk_neighbors", 3): + # given a specified number of dove neighbors and risk level + with patch.object(HawkDoveAgent, "num_dove_neighbors", 3): # an agent with `r=0` will always take the risky choice # (any risk is acceptable). agent.risk_level = 0 agent.choose() assert agent.choice == Play.HAWK - # risk level 2 with 3 hawks will play hawk - # (but this doesn't really make sense...) + # risk level 2 with 3 doves will play dove agent.risk_level = 2 agent.choose() assert agent.choice == Play.HAWK - # risk level three will not + # risk level three with 3 doves will play dove + # (strictly greater comparison) agent.risk_level = 3 agent.choose() assert agent.choice == Play.DOVE From 0b2bba82671ada1ae2440d4d58d7a6dd696e69e7 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 12 Sep 2023 15:58:04 -0400 Subject: [PATCH 073/141] Add risk/wealth plot to hawkdove simulation server --- simulatingrisk/hawkdove/app.py | 19 +++++++++++++++++-- simulatingrisk/hawkdove/server.py | 3 --- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/simulatingrisk/hawkdove/app.py b/simulatingrisk/hawkdove/app.py index b0eea3b..206e34f 100644 --- a/simulatingrisk/hawkdove/app.py +++ b/simulatingrisk/hawkdove/app.py @@ -1,5 +1,8 @@ # solara/jupyterviz app from mesa.experimental import JupyterViz +import pandas as pd +import altair as alt +import solara from simulatingrisk.hawkdove.model import HawkDoveModel from simulatingrisk.hawkdove.server import ( @@ -9,11 +12,23 @@ ) +def plot_wealth(model): + """histogram plot of agent wealth levels across risk levels; + for use with jupyterviz/solara""" + + # generate a histogram of points across risk levels + risk_wealth = [(agent.risk_level, agent.points) for agent in model.schedule.agents] + df = pd.DataFrame(risk_wealth, columns=["risk_level", "wealth"]) + + chart = alt.Chart(df).mark_bar().encode(y="wealth", x="risk_level") + return solara.FigureAltair(chart) + + page = JupyterViz( HawkDoveModel, jupyterviz_params, - measures=[], - # measures=[plot_risk_histogram], + # measures=[], + measures=[plot_wealth], name="Hawk/Dove with risk attitudes", agent_portrayal=agent_portrayal, space_drawer=draw_hawkdove_agent_space, diff --git a/simulatingrisk/hawkdove/server.py b/simulatingrisk/hawkdove/server.py index 3d72f9e..84e7585 100644 --- a/simulatingrisk/hawkdove/server.py +++ b/simulatingrisk/hawkdove/server.py @@ -121,9 +121,6 @@ def draw_hawkdove_agent_space(model, agent_portrayal): hawkdove_domain = ("hawk", "dove") shape_range = ("triangle-up", "circle") - # FIXME: model doesn't get updated when the input changes; - # seems to only get a reference to the initial model - # when risk attitude is variable, # use divergent color scheme to indicate risk level if model.risk_attitudes == "variable": From 6800f1950994c406180dc72d2edde2f2db46e914 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 12 Sep 2023 16:36:17 -0400 Subject: [PATCH 074/141] Implement option for initial hawk odds #22 --- simulatingrisk/hawkdove/model.py | 18 ++++++++++++------ simulatingrisk/hawkdove/server.py | 16 +++++++++++++++- tests/test_hawkdove.py | 11 +++++++++++ 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/simulatingrisk/hawkdove/model.py b/simulatingrisk/hawkdove/model.py index 8453889..fcb08b1 100644 --- a/simulatingrisk/hawkdove/model.py +++ b/simulatingrisk/hawkdove/model.py @@ -33,11 +33,11 @@ class HawkDoveAgent(mesa.Agent): An agent with a risk attitude playing Hawk or Dove """ - def __init__(self, unique_id, model, risk_level=None): + def __init__(self, unique_id, model, risk_level=None, hawk_odds=None): super().__init__(unique_id, model) self.points = 0 - self.choice = self.initial_choice() + self.choice = self.initial_choice(hawk_odds) self.last_choice = None # risk level @@ -46,9 +46,12 @@ def __init__(self, unique_id, model, risk_level=None): num_neighbors = 8 if self.model.include_diagonals else 4 self.risk_level = risk_level or self.random.randint(0, num_neighbors) - def initial_choice(self): - # first round : choose what to play randomly or based on initial setup - return coinflip(play_choices) + def initial_choice(self, hawk_odds=None): + # first round : choose what to play randomly or based on initial hawk odds + opts = {} + if hawk_odds: + opts["weight"] = hawk_odds + return coinflip(play_choices, **opts) @property def neighbors(self): @@ -126,6 +129,7 @@ def __init__( include_diagonals=True, risk_attitudes="variable", agent_risk_level=None, + hawk_odds=0.5, ): super().__init__() # assume a fully-populated square grid @@ -134,6 +138,8 @@ def __init__( # and von neumann (exclude diagonals) self.include_diagonals = include_diagonals self.risk_attitudes = risk_attitudes + # distribution of first choice (50/50 by default) + self.hawk_odds = hawk_odds # initialize a single grid (each square inhabited by a single agent); # configure the grid to wrap around so everyone has neighbors @@ -147,7 +153,7 @@ def __init__( agent_opts["risk_level"] = agent_risk_level for i in range(self.num_agents): - agent = HawkDoveAgent(i, self, **agent_opts) + agent = HawkDoveAgent(i, self, hawk_odds=self.hawk_odds, **agent_opts) self.schedule.add(agent) # place randomly in an empty spot self.grid.move_to_empty(agent) diff --git a/simulatingrisk/hawkdove/server.py b/simulatingrisk/hawkdove/server.py index 84e7585..d8a5e10 100644 --- a/simulatingrisk/hawkdove/server.py +++ b/simulatingrisk/hawkdove/server.py @@ -80,7 +80,21 @@ def agent_portrayal(agent): "values": ["variable", "single"], "description": "Agent initial risk level", }, - "agent_risk_level": {"type": "SliderInt", "min": 0, "max": 8, "step": 1, "value": 2} + "agent_risk_level": { + "type": "SliderInt", + "min": 0, + "max": 8, + "step": 1, + "value": 2, + }, + "hawk_odds": { + "type": "SliderFloat", + "value": 0.5, + "label": "Hawk Odds (first choice)", + "min": 0.0, + "max": 1.0, + "step": 0.1, + }, # "risk_adjustment": { # "type": "Select", # "value": "adopt", diff --git a/tests/test_hawkdove.py b/tests/test_hawkdove.py index 33cab37..bb98c14 100644 --- a/tests/test_hawkdove.py +++ b/tests/test_hawkdove.py @@ -28,6 +28,17 @@ def test_agent_initial_choice(): assert math.isclose(total, half_agents, rel_tol=0.05) +def test_agent_initial_choice_hawkodds(): + grid_size = 100 + # specify hawk-odds other than 05 + model = HawkDoveModel(grid_size, include_diagonals=False, hawk_odds=0.3) + initial_choices = [a.choice for a in model.schedule.agents] + choice_count = Counter(initial_choices) + # expect about 30% hawks + expected_hawks = model.num_agents * 0.3 + assert math.isclose(choice_count[Play.HAWK], expected_hawks, rel_tol=0.05) + + def test_agent_initial_risk_level(): agent = HawkDoveAgent(1, Mock(), risk_level=2) assert agent.risk_level == 2 From 176a2f9815094eabff4382229e13fb597b31989c Mon Sep 17 00:00:00 2001 From: Rebecca Sutton Koeser Date: Wed, 13 Sep 2023 09:37:30 -0400 Subject: [PATCH 075/141] Revise label for 0.5/risk neutral in diagram --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5140d47..c02eb80 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,8 @@ flowchart LR always takes risky choice (any risk is acceptable)"] reu["0.5 -expected utility"] +risk neutral +(expected utility)"] r1["1.0 always takes safe choice (no risk is acceptable)"] From b74bb00632c4320cd46307c30f779e0d424f8cf9 Mon Sep 17 00:00:00 2001 From: Lara Buchak <140551577+LaraBuchak@users.noreply.github.com> Date: Wed, 13 Sep 2023 13:27:50 -0400 Subject: [PATCH 076/141] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c02eb80..84b5ef4 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ The code in this repository is associated with the CDH project [Simulating risk, Simulations are implemented with [Mesa](https://mesa.readthedocs.io/en/stable/), using Agent Based Modeling to explore risk attitudes within populations. -Across simulations, we define agents with risk attitudes tracked via a numeric `r` or `risk_level` 0.0 - 1.0, where `r` is that agent's minimum acceptable risk level. An agent with `r=1` will always take the safe option (no risk is acceptable); an agent with `r=0` will always take the risky choice (any risk is acceptable). +Across simulations, we define agents with risk attitudes tracked via a numeric `r` or `risk_level` 0.0 - 1.0, where `r` is that agent's minimum acceptable risk level for taking the risky bet. An agent with `r=1` will always take the safe option (no risk is acceptable); an agent with `r=0` will always take the risky choice (any risk is acceptable). When the risky bet might be better or worse than the safe bet by the same amount (for example, the risky bet yields 3 or 1 and the safe bet yields 2), r = 0.5 corresponds to expected utility maximization. From c0645178da9ddcc7748d4b6207ecf36076668746 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 13 Sep 2023 16:15:18 -0400 Subject: [PATCH 077/141] Revise dove payoff to 2.1 to make EU maximizer clearer --- simulatingrisk/hawkdove/README.md | 2 +- simulatingrisk/hawkdove/model.py | 12 ++++++++---- tests/test_hawkdove.py | 6 +++--- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/simulatingrisk/hawkdove/README.md b/simulatingrisk/hawkdove/README.md index d0fe99a..5f8409e 100644 --- a/simulatingrisk/hawkdove/README.md +++ b/simulatingrisk/hawkdove/README.md @@ -9,7 +9,7 @@ This is a variant of the Hawk/Dove Game: https://en.wikipedia.org/wiki/Chicken_( | | H | D| |-|-|-| | H | 0, 0 | 3, 1| -| D |1, 3| 2, 2| +| D |1, 3| 2.1, 2.1| BACKGROUND: An unpublished paper by Blessenohl shows that the equilibrium in this game is different for EU maximizers than for REU maximizers (all with the same risk-attitude), and that REU maximizers do better as a population (basically, play DOVE more often) diff --git a/simulatingrisk/hawkdove/model.py b/simulatingrisk/hawkdove/model.py index fcb08b1..c835d8c 100644 --- a/simulatingrisk/hawkdove/model.py +++ b/simulatingrisk/hawkdove/model.py @@ -53,6 +53,10 @@ def initial_choice(self, hawk_odds=None): opts["weight"] = hawk_odds return coinflip(play_choices, **opts) + @property + def choice_label(self): + return "hawk" if self.choice == Play.HAWK else "dove" + @property def neighbors(self): # use configured neighborhood (with or without diagonals) on the model; @@ -70,7 +74,7 @@ def choose(self): "decide what to play this round" # after the first round, choose based on what neighbors did last time if self.model.schedule.steps > 0: - # store previous choice + # store previous choice self.last_choice = self.choice # choose based on the number of neighbors who played @@ -96,7 +100,7 @@ def play(self): def payoff(self, other): """ If I play HAWK and neighbor plays DOVE: 3 - If I play DOVE and neighbor plays DOVE: 2 + If I play DOVE and neighbor plays DOVE: 2.1 If I play DOVE and neighbor plays HAWK: 1 If I play HAWK and neighbor plays HAWK: 0 """ @@ -107,7 +111,7 @@ def payoff(self, other): return 0 elif self.choice == Play.DOVE: if other.choice == Play.DOVE: - return 2 + return 2.1 if other.choice == Play.HAWK: return 1 @@ -160,7 +164,7 @@ def __init__( self.datacollector = mesa.DataCollector( model_reporters={"max_agent_points": "max_agent_points"}, - agent_reporters={"risk_level": "risk_level", "choice": "choice"}, + agent_reporters={"risk_level": "risk_level", "choice": "choice_label"}, ) def step(self): diff --git a/tests/test_hawkdove.py b/tests/test_hawkdove.py index bb98c14..0dda587 100644 --- a/tests/test_hawkdove.py +++ b/tests/test_hawkdove.py @@ -116,7 +116,7 @@ def test_agent_choose(): def test_agent_payoff(): # If I play HAWK and neighbor plays DOVE: 3 - # If I play DOVE and neighbor plays DOVE: 2 + # If I play DOVE and neighbor plays DOVE: 2.1 # If I play DOVE and neighbor plays HAWK: 1 # If I play HAWK and neighbor plays HAWK: 0 @@ -137,5 +137,5 @@ def test_agent_payoff(): # if both play dove, payoff is two for both agent.choice = Play.DOVE other_agent.choice = Play.DOVE - assert agent.payoff(other_agent) == 2 - assert other_agent.payoff(agent) == 2 + assert agent.payoff(other_agent) == 2.1 + assert other_agent.payoff(agent) == 2.1 From 6e1c6de665d1fd021cef479b43e7c035e6a0c938 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 13 Sep 2023 17:06:21 -0400 Subject: [PATCH 078/141] Plot % hawks when displaying hawk/dove game --- simulatingrisk/hawkdove/app.py | 27 +++++++++++++++++++++++++-- simulatingrisk/hawkdove/model.py | 11 ++++++++++- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/simulatingrisk/hawkdove/app.py b/simulatingrisk/hawkdove/app.py index 206e34f..a3525a4 100644 --- a/simulatingrisk/hawkdove/app.py +++ b/simulatingrisk/hawkdove/app.py @@ -4,6 +4,7 @@ import altair as alt import solara + from simulatingrisk.hawkdove.model import HawkDoveModel from simulatingrisk.hawkdove.server import ( agent_portrayal, @@ -24,11 +25,33 @@ def plot_wealth(model): return solara.FigureAltair(chart) +def plot_hawks(model): + """plot percent of agents who chose hawk over last several rounds; + for use with jupyterviz/solara""" + + model_df = model.datacollector.get_model_vars_dataframe().reset_index() + + # limit to last N rounds (how many ?) + last_n_rounds = model_df.tail(50) + chart = ( + alt.Chart(last_n_rounds) + .mark_bar(color="orange") + .encode( + x=alt.X("index", title="Step"), + y=alt.Y( + "percent_hawk", + title="Percent who chose hawk", + scale=alt.Scale(domain=[0, 1]), + ), + ) + ) + return solara.FigureAltair(chart) + + page = JupyterViz( HawkDoveModel, jupyterviz_params, - # measures=[], - measures=[plot_wealth], + measures=[plot_hawks], name="Hawk/Dove with risk attitudes", agent_portrayal=agent_portrayal, space_drawer=draw_hawkdove_agent_space, diff --git a/simulatingrisk/hawkdove/model.py b/simulatingrisk/hawkdove/model.py index c835d8c..94236d2 100644 --- a/simulatingrisk/hawkdove/model.py +++ b/simulatingrisk/hawkdove/model.py @@ -163,7 +163,10 @@ def __init__( self.grid.move_to_empty(agent) self.datacollector = mesa.DataCollector( - model_reporters={"max_agent_points": "max_agent_points"}, + model_reporters={ + "max_agent_points": "max_agent_points", + "percent_hawk": "percent_hawk", + }, agent_reporters={"risk_level": "risk_level", "choice": "choice_label"}, ) @@ -178,3 +181,9 @@ def step(self): def max_agent_points(self): # what is the current largest point total of any agent? return max([a.points for a in self.schedule.agents]) + + @property + def percent_hawk(self): + # what percent of agents chose hawk? + hawks = [a for a in self.schedule.agents if a.choice == Play.HAWK] + return len(hawks) / self.num_agents From c61a74219d7898eb4deefd98d51e25831b5ad803 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 13 Sep 2023 17:06:41 -0400 Subject: [PATCH 079/141] Depend on bleeding edge mesa for latest jupyterviz code --- pyproject.toml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 39f4617..8e508a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,8 +11,10 @@ classifiers = [ "Programming Language :: Python :: 3", ] dependencies = [ - "mesa>=2.1", - "matplotlib" + # "mesa>=2.1", + "mesa @ git+https://github.com/projectmesa/mesa.git@main", + "matplotlib", + "altair>5.0.1" ] dynamic = ["version", "readme"] From b5730dbabdabf6baf03b9a65795f32cb294129b3 Mon Sep 17 00:00:00 2001 From: Rebecca Sutton Koeser Date: Wed, 13 Sep 2023 18:27:30 -0400 Subject: [PATCH 080/141] =?UTF-8?q?Update=20EU=20maximizer=20to=200.5?= =?UTF-8?q?=E2=81=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 84b5ef4..a504fce 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ The code in this repository is associated with the CDH project [Simulating risk, Simulations are implemented with [Mesa](https://mesa.readthedocs.io/en/stable/), using Agent Based Modeling to explore risk attitudes within populations. -Across simulations, we define agents with risk attitudes tracked via a numeric `r` or `risk_level` 0.0 - 1.0, where `r` is that agent's minimum acceptable risk level for taking the risky bet. An agent with `r=1` will always take the safe option (no risk is acceptable); an agent with `r=0` will always take the risky choice (any risk is acceptable). When the risky bet might be better or worse than the safe bet by the same amount (for example, the risky bet yields 3 or 1 and the safe bet yields 2), r = 0.5 corresponds to expected utility maximization. +Across simulations, we define agents with risk attitudes tracked via a numeric `r` or `risk_level` 0.0 - 1.0, where `r` is that agent's minimum acceptable risk level for taking the risky bet. An agent with `r=1` will always take the safe option (no risk is acceptable); an agent with `r=0` will always take the risky choice (any risk is acceptable). When the risky bet might be better or worse than the safe bet by the same amount (for example, the risky bet yields 3 or 1 and the safe bet yields 2), `r = 0.5⁻` corresponds to expected utility maximization. @@ -16,9 +16,9 @@ flowchart LR r0["0.0 always takes risky choice (any risk is acceptable)"] - reu["0.5 + reu["0.5⁻ risk neutral -(expected utility)"] +(expected utility maximizer)"] r1["1.0 always takes safe choice (no risk is acceptable)"] From feceb824b9883a409be6f9e7c428e61494247e99 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 20 Sep 2023 09:26:29 -0400 Subject: [PATCH 081/141] Update all apps and charts to work with current mesa / jupyterviz --- simulatingrisk/charts/histogram.py | 6 +++--- simulatingrisk/risky_food/app.py | 2 +- simulatingrisk/risky_food/server.py | 8 +++----- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/simulatingrisk/charts/histogram.py b/simulatingrisk/charts/histogram.py index c3123b1..46c9d6a 100644 --- a/simulatingrisk/charts/histogram.py +++ b/simulatingrisk/charts/histogram.py @@ -55,7 +55,7 @@ def __init__(self, bins, canvas_height, canvas_width, label="risk levels"): r += 0.1 -def plot_risk_histogram(viz): +def plot_risk_histogram(model): """histogram plot of agent risk levels; for use with jupyterviz/solara""" # adapted from mesa visualiation tutorial @@ -66,11 +66,11 @@ def plot_risk_histogram(viz): fig = Figure() ax = fig.subplots() # generate a histogram of current risk levels - risk_levels = [agent.risk_level for agent in viz.model.schedule.agents] + risk_levels = [agent.risk_level for agent in model.schedule.agents] # Note: you have to use Matplotlib's OOP API instead of plt.hist # because plt.hist is not thread-safe. ax.hist(risk_levels, bins=risk_bins) ax.set_title("risk levels") # You have to specify the dependencies as follows, so that the figure # auto-updates when viz.model or viz.df is changed. - solara.FigureMatplotlib(fig, dependencies=[viz.model, viz.df]) + solara.FigureMatplotlib(fig, dependencies=[model]) diff --git a/simulatingrisk/risky_food/app.py b/simulatingrisk/risky_food/app.py index 34cb05a..1255e51 100644 --- a/simulatingrisk/risky_food/app.py +++ b/simulatingrisk/risky_food/app.py @@ -13,7 +13,7 @@ jupyterviz_params, measures=[plot_total_agents, plot_risk_histogram], name="Risky Food", - # no agent portrayal because this model does not use a grid + space_drawer=None, # no agent portrayal because this model does not use a grid ) # required to render the visualization with Jupyter/Solara page diff --git a/simulatingrisk/risky_food/server.py b/simulatingrisk/risky_food/server.py index d85e96e..c84d594 100644 --- a/simulatingrisk/risky_food/server.py +++ b/simulatingrisk/risky_food/server.py @@ -67,14 +67,12 @@ } -def plot_total_agents(viz): +def plot_total_agents(model): """plot total agents over time to provide an indicator of population size""" fig = Figure() ax = fig.subplots() # generate a line plot of total number of agents - model_df = viz.model.datacollector.get_model_vars_dataframe() + model_df = model.datacollector.get_model_vars_dataframe() ax.plot(model_df.num_agents) ax.set_title("total agents") - # You have to specify the dependencies as follows, so that the figure - # auto-updates when viz.model or viz.df is changed. - solara.FigureMatplotlib(fig, dependencies=[viz.model, viz.df]) + solara.FigureMatplotlib(fig) From 3d77cc77eea7178a715c5ee021f5a6a2b5068082 Mon Sep 17 00:00:00 2001 From: Lara Buchak <140551577+LaraBuchak@users.noreply.github.com> Date: Tue, 26 Sep 2023 08:27:31 -0400 Subject: [PATCH 082/141] Update README.md --- simulatingrisk/hawkdove/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simulatingrisk/hawkdove/README.md b/simulatingrisk/hawkdove/README.md index 5f8409e..86d1618 100644 --- a/simulatingrisk/hawkdove/README.md +++ b/simulatingrisk/hawkdove/README.md @@ -11,7 +11,7 @@ This is a variant of the Hawk/Dove Game: https://en.wikipedia.org/wiki/Chicken_( | H | 0, 0 | 3, 1| | D |1, 3| 2.1, 2.1| -BACKGROUND: An unpublished paper by Blessenohl shows that the equilibrium in this game is different for EU maximizers than for REU maximizers (all with the same risk-attitude), and that REU maximizers do better as a population (basically, play DOVE more often) +BACKGROUND: An unpublished paper by Simon Blessenohl shows that the equilibrium in this game is different for EU maximizers than for REU maximizers (all with the same risk-attitude), and that REU maximizers do better as a population (basically, play DOVE more often) We want to know: what happens when different people have _different_ risk-attitudes. From bb5dceb49de10b802c6ef8427c9d636c1e111964 Mon Sep 17 00:00:00 2001 From: Lara Buchak <140551577+LaraBuchak@users.noreply.github.com> Date: Tue, 26 Sep 2023 08:43:02 -0400 Subject: [PATCH 083/141] Update README.md --- simulatingrisk/hawkdove/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/simulatingrisk/hawkdove/README.md b/simulatingrisk/hawkdove/README.md index 86d1618..82fe847 100644 --- a/simulatingrisk/hawkdove/README.md +++ b/simulatingrisk/hawkdove/README.md @@ -29,6 +29,10 @@ Players arranged on a lattice [try both 4 neighbors (AYBD) and 8 neighbors (XYZA Each player on a lattice (grid in Mesa): - Has parameter `r` [from 0 to 8, or 0 to 4 for four neighbors] - Let `d` be the number of neighbors who played DOVE during the previous round. If `d > r`, then play HAWK. Otherwise play DOVE. (Agents who are risk-avoidant only play HAWK if there are a lot of doves around them. More risk-avoidance requires a higher number of doves to get an agent to play HAWK.) +- The proportion of neighbors who play DOVE corresponds to your probability of encountering a DOVE when playing a randomly-selected neighbor. The intended interpretation is that you maximize REU for this probability of your opponent playing DOVE. Thus, r corresponds to the probability above which playing HAWK maximizes REU. +- An REU maximizer will play HAWK when r(p) > [(D,H)-(H,H)]/[(H,D)-(D,D)] ; in other words, when r(p) > 0.52. An EU maximizer, with r(p) = p, will play HAWK when p > 0.52, e.g., when more than 4 out of 8 neighbors play DOVE. Thus, r = 4 corresponds to risk-neutrality (EU maximization), r < 4 corresponds to risk-inclination, and r > 4 corresponds to risk-avoidance. +- Payoffs were chosen to avoid the case in which two choices had equal expected utility for some number of neighbors. For example, if the payoff of (D,D) was (2,2), then at p = 0.5 (4 of 8 neighbors), then EU maximizers would be indifferent between HAWK and DOVE; in this case, no r-value would correspond to EU maximization, since r = 4 strictly prefers DOVE and r = 3 strictly prefers HAWK. + - Choice for the first round could be randomly determined, or add parameters to see how initial conditions matter? - [OR VARY FIRST ROUND: what proportion starts as HAWK - [Who is a HAWK and who is a DOVE is randomly determined; proportion set at the beginning of each simulation. E.g. 30% are HAWKS; if we have 100 players, then each player has a 30% chance of being HAWK] From 92a7f4277d28a78ad8dc841738c6b9f21f35feb7 Mon Sep 17 00:00:00 2001 From: Lara Buchak <140551577+LaraBuchak@users.noreply.github.com> Date: Tue, 26 Sep 2023 08:44:14 -0400 Subject: [PATCH 084/141] Update README.md --- simulatingrisk/hawkdove/README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/simulatingrisk/hawkdove/README.md b/simulatingrisk/hawkdove/README.md index 82fe847..51829fe 100644 --- a/simulatingrisk/hawkdove/README.md +++ b/simulatingrisk/hawkdove/README.md @@ -25,6 +25,12 @@ Players arranged on a lattice [try both 4 neighbors (AYBD) and 8 neighbors (XYZA |A | **I** | B | | C | D | E | +- Payoffs are determined as follows: + - Look at what each neighbor did, then: + - If I play HAWK and neighbor plays DOVE: 3 + - If I play DOVE and neighbor plays DOVE: 2.1 + - If I play DOVE and neighbor plays HAWK: 1 + - If I play HAWK and neighbor plays HAWK: 0 Each player on a lattice (grid in Mesa): - Has parameter `r` [from 0 to 8, or 0 to 4 for four neighbors] @@ -37,11 +43,6 @@ Each player on a lattice (grid in Mesa): - [OR VARY FIRST ROUND: what proportion starts as HAWK - [Who is a HAWK and who is a DOVE is randomly determined; proportion set at the beginning of each simulation. E.g. 30% are HAWKS; if we have 100 players, then each player has a 30% chance of being HAWK] - Call this initial parameter HAWK-ODDS -- Payoffs are determined as follows: - - Look at what each neighbor did, then: - - If I play HAWK and neighbor plays DOVE: 3 - - If I play DOVE and neighbor plays DOVE: 2 - - If I play DOVE and neighbor plays HAWK: 1 - - If I play HAWK and neighbor plays HAWK: 0 + From 566844f291c4f2fb5c31af2e17c8f28acb255ba4 Mon Sep 17 00:00:00 2001 From: Lara Buchak <140551577+LaraBuchak@users.noreply.github.com> Date: Tue, 26 Sep 2023 08:56:22 -0400 Subject: [PATCH 085/141] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a504fce..b638797 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ The code in this repository is associated with the CDH project [Simulating risk, Simulations are implemented with [Mesa](https://mesa.readthedocs.io/en/stable/), using Agent Based Modeling to explore risk attitudes within populations. -Across simulations, we define agents with risk attitudes tracked via a numeric `r` or `risk_level` 0.0 - 1.0, where `r` is that agent's minimum acceptable risk level for taking the risky bet. An agent with `r=1` will always take the safe option (no risk is acceptable); an agent with `r=0` will always take the risky choice (any risk is acceptable). When the risky bet might be better or worse than the safe bet by the same amount (for example, the risky bet yields 3 or 1 and the safe bet yields 2), `r = 0.5⁻` corresponds to expected utility maximization. +Across simulations, we define agents with risk attitudes tracked via a numeric `r` or `risk_level` 0.0 - 1.0, where `r` is that agent's minimum acceptable risk level for taking the risky bet. When the probability 'p' of the risky bet paying off is greater than an agent's 'r', that agent will take the bet. An agent with `r=1` will always take the safe option (no risk is acceptable); an agent with `r=0` will always take the risky choice (any risk is acceptable). Notice that the agent is never indifferent; allowing indifference would require introducing a tie-breaking rule, which would be a further parameter. + +When the risky bet might be better or worse than the safe bet by the same amount (for example, the risky bet yields 3 or 1 and the safe bet yields 2), an agent who maximizes expected utility will prefer the risky bet when 'p > 0.5' and will prefer the safe bet when 'p < 0.5'; and they will be indifferent between the risky bet and the safe bet. Thus, 'r = 0.5' corresponds to expected utility maximization except in the case in which the probability is exactly 0.5 (or, we might say, a point 'epsilon' units to the left of 0.5, where 'epsilon' is smaller than the fineness of our random number generator, corresponds to expected utility maximization). These complications make no difference in practice, so we can simply say that 'r = 0.5' corresponds to expected utility maximization. From 4dcf4433c751b2a43ebcb667d75f6826d59f19e0 Mon Sep 17 00:00:00 2001 From: Lara Buchak <140551577+LaraBuchak@users.noreply.github.com> Date: Tue, 26 Sep 2023 08:57:05 -0400 Subject: [PATCH 086/141] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b638797..87e3474 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Simulations are implemented with [Mesa](https://mesa.readthedocs.io/en/stable/), Across simulations, we define agents with risk attitudes tracked via a numeric `r` or `risk_level` 0.0 - 1.0, where `r` is that agent's minimum acceptable risk level for taking the risky bet. When the probability 'p' of the risky bet paying off is greater than an agent's 'r', that agent will take the bet. An agent with `r=1` will always take the safe option (no risk is acceptable); an agent with `r=0` will always take the risky choice (any risk is acceptable). Notice that the agent is never indifferent; allowing indifference would require introducing a tie-breaking rule, which would be a further parameter. -When the risky bet might be better or worse than the safe bet by the same amount (for example, the risky bet yields 3 or 1 and the safe bet yields 2), an agent who maximizes expected utility will prefer the risky bet when 'p > 0.5' and will prefer the safe bet when 'p < 0.5'; and they will be indifferent between the risky bet and the safe bet. Thus, 'r = 0.5' corresponds to expected utility maximization except in the case in which the probability is exactly 0.5 (or, we might say, a point 'epsilon' units to the left of 0.5, where 'epsilon' is smaller than the fineness of our random number generator, corresponds to expected utility maximization). These complications make no difference in practice, so we can simply say that 'r = 0.5' corresponds to expected utility maximization. +When the risky bet might be better or worse than the safe bet by the same amount (for example, the risky bet yields 3 or 1 and the safe bet yields 2), an agent who maximizes expected utility will prefer the risky bet when p > 0.5 and will prefer the safe bet when 'p < 0.5'; and they will be indifferent between the risky bet and the safe bet. Thus, r = 0.5 corresponds to expected utility maximization except in the case in which the probability is exactly 0.5 (or, we might say, a point epsilon units to the left of 0.5, where epsilon is smaller than the fineness of our random number generator, corresponds to expected utility maximization). These complications make no difference in practice, so we can simply say that r = 0.5 corresponds to expected utility maximization. From e7a6d97704ac7f6395e49145a894629897d072d6 Mon Sep 17 00:00:00 2001 From: Lara Buchak <140551577+LaraBuchak@users.noreply.github.com> Date: Tue, 26 Sep 2023 09:02:59 -0400 Subject: [PATCH 087/141] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 87e3474..947d191 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ flowchart LR r0["0.0 always takes risky choice (any risk is acceptable)"] - reu["0.5⁻ + reu["0.5 risk neutral (expected utility maximizer)"] r1["1.0 From c4b72efe6c97086f43ebffc161dcc19d067ab123 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 26 Sep 2023 11:29:04 -0400 Subject: [PATCH 088/141] Preliminary batch run for hawk/dove with single risk attitude --- simulatingrisk/batch_run.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/simulatingrisk/batch_run.py b/simulatingrisk/batch_run.py index 8a21330..2a3c550 100755 --- a/simulatingrisk/batch_run.py +++ b/simulatingrisk/batch_run.py @@ -6,6 +6,7 @@ from mesa import batch_run +from simulatingrisk.hawkdove.model import HawkDoveModel from simulatingrisk.risky_bet.model import RiskyBetModel from simulatingrisk.risky_food.model import RiskyFoodModel @@ -46,6 +47,28 @@ def riskyfood_batch_run(): return results +def hawkdove_batch_run(): + # params are: + # grid_size, + # include_diagonals=True, + # risk_attitudes="variable", or single + # agent_risk_level=None, + # hawk_odds=0.5, + return batch_run( + HawkDoveModel, + # when including diagonals, risk levels go from 0 to 8; + # probably do not need to include the extremes for this analysis + parameters={ + "grid_size": 20, + "risk_attitudes": "single", + "agent_risk_level": [0, 1, 2, 3, 4, 5, 6, 7, 8], + }, + number_processes=1, + data_collection_period=1, + display_progress=True, + ) + + def save_results(simulation, results): # save as csv for external analysis # - use datetime to distinguish this run, but make nicer for filename @@ -68,12 +91,14 @@ def save_results(simulation, results): prog="simulatingrisk batch_run", description="Run simulations in batch mode and save collected data", ) - parser.add_argument("simulation", choices=["riskybet", "riskyfood"]) + parser.add_argument("simulation", choices=["riskybet", "riskyfood", "hawkdove"]) args = parser.parse_args() if args.simulation == "riskybet": results = riskybet_batch_run() elif args.simulation == "riskyfood": results = riskyfood_batch_run() + elif args.simulation == "hawkdove": + results = hawkdove_batch_run() save_results(args.simulation, results) From 43136a44df72532543522064973536274e15b278 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 26 Sep 2023 15:05:22 -0400 Subject: [PATCH 089/141] Run models as subcommands; add single/variable risk option to hawk/dove --- simulatingrisk/batch_run.py | 68 +++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 21 deletions(-) diff --git a/simulatingrisk/batch_run.py b/simulatingrisk/batch_run.py index 2a3c550..6c89ca4 100755 --- a/simulatingrisk/batch_run.py +++ b/simulatingrisk/batch_run.py @@ -11,7 +11,7 @@ from simulatingrisk.risky_food.model import RiskyFoodModel -def riskybet_batch_run(): +def riskybet_batch_run(args): results = batch_run( RiskyBetModel, parameters={ @@ -30,10 +30,10 @@ def riskybet_batch_run(): display_progress=True, ) # returns a list of dictionaries from data collection across all runs - return results + save_results("riskybet", results) -def riskyfood_batch_run(): +def riskyfood_batch_run(args): results = batch_run( RiskyFoodModel, # only parameter to this one currently is number of agents @@ -44,29 +44,48 @@ def riskyfood_batch_run(): data_collection_period=1, display_progress=True, ) - return results + save_results("riskyfood", results) -def hawkdove_batch_run(): +def hawkdove_batch_run(args): # params are: # grid_size, # include_diagonals=True, # risk_attitudes="variable", or single # agent_risk_level=None, # hawk_odds=0.5, - return batch_run( - HawkDoveModel, - # when including diagonals, risk levels go from 0 to 8; - # probably do not need to include the extremes for this analysis - parameters={ + + if args.risk_attitudes == "variable": + params = { + "grid_size": 20, + "risk_attitudes": "variable", + } + iterations = 5 + elif args.risk_attitudes == "single": + params = { "grid_size": 20, "risk_attitudes": "single", "agent_risk_level": [0, 1, 2, 3, 4, 5, 6, 7, 8], - }, + } + iterations = 1 + + results = batch_run( + HawkDoveModel, + # when including diagonals, risk levels go from 0 to 8; + # probably do not need to include the extremes for this analysis + parameters=params, + # "grid_size": 20, + # "risk_attitudes": "variable", + # "risk_attitudes": "single", + # "agent_risk_level": [0, 1, 2, 3, 4, 5, 6, 7, 8], + # }, + iterations=iterations, number_processes=1, data_collection_period=1, display_progress=True, ) + # include the mode in the output filename + save_results("hawkdove_risk-%s" % args.risk_attitudes, results) def save_results(simulation, results): @@ -91,14 +110,21 @@ def save_results(simulation, results): prog="simulatingrisk batch_run", description="Run simulations in batch mode and save collected data", ) - parser.add_argument("simulation", choices=["riskybet", "riskyfood", "hawkdove"]) - args = parser.parse_args() - - if args.simulation == "riskybet": - results = riskybet_batch_run() - elif args.simulation == "riskyfood": - results = riskyfood_batch_run() - elif args.simulation == "hawkdove": - results = hawkdove_batch_run() + # use subcommands so we can add model-specific options + subparsers = parser.add_subparsers(help="Help for model-specific options") + riskybet_parser = subparsers.add_parser("riskybet") + riskybet_parser.set_defaults(func=riskybet_batch_run) + riskyfood_parser = subparsers.add_parser("riskyfood") + riskyfood_parser.set_defaults(func=riskyfood_batch_run) + hawkdove_parser = subparsers.add_parser("hawkdove") + hawkdove_parser.add_argument( + "-r", + "--risk-attitudes", + choices=["single", "variable"], + help="Mode for initializing agent risk attitudes", + ) + hawkdove_parser.set_defaults(func=hawkdove_batch_run) - save_results(args.simulation, results) + args = parser.parse_args() + # run appropriate function based on the selected subcommand + args.func(args) From 1c95b67b327596762f0046f778f5ae36336e5662 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Thu, 28 Sep 2023 10:30:40 -0400 Subject: [PATCH 090/141] Include rolling average to % hawk chart for hawk/dove app --- simulatingrisk/hawkdove/app.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/simulatingrisk/hawkdove/app.py b/simulatingrisk/hawkdove/app.py index a3525a4..af0bfea 100644 --- a/simulatingrisk/hawkdove/app.py +++ b/simulatingrisk/hawkdove/app.py @@ -31,9 +31,12 @@ def plot_hawks(model): model_df = model.datacollector.get_model_vars_dataframe().reset_index() + # calculate a rolling average for % hawk + model_df["rollingavg_percent_hawk"] = model_df.percent_hawk.rolling(10).mean() + # limit to last N rounds (how many ?) last_n_rounds = model_df.tail(50) - chart = ( + bar_chart = ( alt.Chart(last_n_rounds) .mark_bar(color="orange") .encode( @@ -45,7 +48,21 @@ def plot_hawks(model): ), ) ) - return solara.FigureAltair(chart) + # graph rolling average as a line over the bar chart + line = ( + alt.Chart(last_n_rounds) + .mark_line(color="blue") + .encode( + x=alt.X("index", title="Step"), + y=alt.Y( + "rollingavg_percent_hawk", + title="% hawk (rolling average)", + scale=alt.Scale(domain=[0, 1]), + ), + ) + ) + + return solara.FigureAltair(bar_chart + line) page = JupyterViz( From 5a893244ef58794555f71fa933757ec48a4da6ab Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Thu, 28 Sep 2023 10:43:50 -0400 Subject: [PATCH 091/141] Handle risk level zero properly; include points in data collection --- simulatingrisk/hawkdove/model.py | 19 ++++++++++++++++--- tests/test_hawkdove.py | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/simulatingrisk/hawkdove/model.py b/simulatingrisk/hawkdove/model.py index 94236d2..8db662f 100644 --- a/simulatingrisk/hawkdove/model.py +++ b/simulatingrisk/hawkdove/model.py @@ -44,7 +44,16 @@ def __init__(self, unique_id, model, risk_level=None, hawk_odds=None): # - based partially on neighborhood size, # which is configurable at the model level num_neighbors = 8 if self.model.include_diagonals else 4 - self.risk_level = risk_level or self.random.randint(0, num_neighbors) + # if risk level is None, generate a random risk level + # NOTE: this means we allow risk level zero + if risk_level is None: + self.risk_level = self.random.randint(0, num_neighbors) + else: + # otherwise, used as passed + self.risk_level = risk_level + + def __repr__(self): + return f"" def initial_choice(self, hawk_odds=None): # first round : choose what to play randomly or based on initial hawk odds @@ -153,7 +162,7 @@ def __init__( agent_opts = {} # when started in single risk attitude mode, initialize all agents # with the specified risk level - if risk_attitudes == "single" and agent_risk_level: + if risk_attitudes == "single" and agent_risk_level is not None: agent_opts["risk_level"] = agent_risk_level for i in range(self.num_agents): @@ -167,7 +176,11 @@ def __init__( "max_agent_points": "max_agent_points", "percent_hawk": "percent_hawk", }, - agent_reporters={"risk_level": "risk_level", "choice": "choice_label"}, + agent_reporters={ + "risk_level": "risk_level", + "choice": "choice_label", + "points": "points", + }, ) def step(self): diff --git a/tests/test_hawkdove.py b/tests/test_hawkdove.py index 0dda587..93ce8ef 100644 --- a/tests/test_hawkdove.py +++ b/tests/test_hawkdove.py @@ -44,6 +44,13 @@ def test_agent_initial_risk_level(): assert agent.risk_level == 2 +def test_agent_repr(): + agent_id = 1 + risk_level = 3 + agent = HawkDoveAgent(agent_id, Mock(), risk_level=risk_level) + assert repr(agent) == f"" + + def test_model_single_risk_level(): risk_level = 3 model = HawkDoveModel( @@ -52,6 +59,14 @@ def test_model_single_risk_level(): for agent in model.schedule.agents: assert agent.risk_level == risk_level + # handle zero properly (should not be treated the same as None) + risk_level = 0 + model = HawkDoveModel( + 5, include_diagonals=True, risk_attitudes="single", agent_risk_level=risk_level + ) + for agent in model.schedule.agents: + assert agent.risk_level == risk_level + def test_model_variable_risk_level(): model = HawkDoveModel(5, include_diagonals=True, risk_attitudes="variable") From 5762569ca7416d171ec16cb8529128105a46d967 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Thu, 28 Sep 2023 10:44:45 -0400 Subject: [PATCH 092/141] Update batch run tests --- simulatingrisk/batch_run.py | 12 +++++++++--- tests/test_batch_run.py | 14 ++++++++------ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/simulatingrisk/batch_run.py b/simulatingrisk/batch_run.py index 6c89ca4..dbfb5de 100755 --- a/simulatingrisk/batch_run.py +++ b/simulatingrisk/batch_run.py @@ -11,7 +11,7 @@ from simulatingrisk.risky_food.model import RiskyFoodModel -def riskybet_batch_run(args): +def riskybet_batch_run(args=None): results = batch_run( RiskyBetModel, parameters={ @@ -33,7 +33,7 @@ def riskybet_batch_run(args): save_results("riskybet", results) -def riskyfood_batch_run(args): +def riskyfood_batch_run(args=None): results = batch_run( RiskyFoodModel, # only parameter to this one currently is number of agents @@ -83,6 +83,7 @@ def hawkdove_batch_run(args): number_processes=1, data_collection_period=1, display_progress=True, + max_steps=200, # converges very quickly, so don't run 1000 times ) # include the mode in the output filename save_results("hawkdove_risk-%s" % args.risk_attitudes, results) @@ -127,4 +128,9 @@ def save_results(simulation, results): args = parser.parse_args() # run appropriate function based on the selected subcommand - args.func(args) + # if a subcommand is not specified, no function is set + if hasattr(args, "func"): + args.func(args) + else: + parser.print_help() + exit(-1) diff --git a/tests/test_batch_run.py b/tests/test_batch_run.py index e7d90d9..893edcd 100644 --- a/tests/test_batch_run.py +++ b/tests/test_batch_run.py @@ -14,11 +14,12 @@ # patch mesa.batch_run in context of local batch run script @patch("simulatingrisk.batch_run.batch_run") -def test_riskybet_batch_run(mock_batch_run): +@patch("simulatingrisk.batch_run.save_results") +def test_riskybet_batch_run(mock_save_results, mock_batch_run): # assert mesa batch run is called as expected # FIXME: this test is too brittle, # as written has to be updated everytime we change batch run options - results = riskybet_batch_run() + riskybet_batch_run() mock_batch_run.assert_called_with( RiskyBetModel, parameters={ @@ -33,14 +34,15 @@ def test_riskybet_batch_run(mock_batch_run): data_collection_period=1, display_progress=True, ) - assert results == mock_batch_run.return_value + mock_save_results.assert_called_with("riskybet", mock_batch_run.return_value) # patch mesa.batch_run in context of local batch run script @patch("simulatingrisk.batch_run.batch_run") -def test_riskyfood_batch_run(mock_batch_run): +@patch("simulatingrisk.batch_run.save_results") +def test_riskyfood_batch_run(mock_save_results, mock_batch_run): # assert mesa batch run is called as expected - results = riskyfood_batch_run() + riskyfood_batch_run() mock_batch_run.assert_called_with( RiskyFoodModel, parameters={"n": 110, "mode": "types"}, @@ -50,7 +52,7 @@ def test_riskyfood_batch_run(mock_batch_run): data_collection_period=1, display_progress=True, ) - assert results == mock_batch_run.return_value + mock_save_results.assert_called_with("riskyfood", mock_batch_run.return_value) def test_save_results(capsys, tmpdir): From 3965ff403030698b612220a58712b4308fb97357 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Fri, 6 Oct 2023 12:53:11 -0400 Subject: [PATCH 093/141] Notebooks for analyzing batch runs of single/variable risk hawk/dove ref #19 --- notebooks/hawkdove_single_r_analysis.ipynb | 2740 ++++++++++++++++ notebooks/hawkdove_variable_r_analysis.ipynb | 3037 ++++++++++++++++++ 2 files changed, 5777 insertions(+) create mode 100644 notebooks/hawkdove_single_r_analysis.ipynb create mode 100644 notebooks/hawkdove_variable_r_analysis.ipynb diff --git a/notebooks/hawkdove_single_r_analysis.ipynb b/notebooks/hawkdove_single_r_analysis.ipynb new file mode 100644 index 0000000..7ad84ce --- /dev/null +++ b/notebooks/hawkdove_single_r_analysis.ipynb @@ -0,0 +1,2740 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "id": "60d1f875-01f6-41ea-9e9d-385271fba925", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "436fe77f-c9a6-4bf2-a395-b496f4a35a59", + "metadata": {}, + "outputs": [], + "source": [ + "#df = pd.read_csv(\"../hawkdove_2023-09-25T150643_380857.csv\")\n", + "# more recent run that includes data collection for agent cumulative points\n", + "# df = pd.read_csv(\"../hawkdove_risk-single_2023-09-27T154226_942256.csv\")\n", + "# smaller version - only 200 rounds\n", + "df = pd.read_csv(\"../hawkdove_risk-single_2023-09-27T175109_555307.csv\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "bb714902-b6dd-4bdd-9640-df0df52979d9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
RunIditerationStepgrid_sizerisk_attitudesagent_risk_levelmax_agent_pointspercent_hawkAgentIDrisk_levelchoicepoints
000020single021.00.5625NaNNaNNaNNaN
100120single030.00.76000.00.0dove13.5
200120single030.00.76001.00.0dove11.3
300120single030.00.76002.00.0dove13.5
400120single030.00.76003.00.0hawk6.0
\n", + "
" + ], + "text/plain": [ + " RunId iteration Step grid_size risk_attitudes agent_risk_level \n", + "0 0 0 0 20 single 0 \\\n", + "1 0 0 1 20 single 0 \n", + "2 0 0 1 20 single 0 \n", + "3 0 0 1 20 single 0 \n", + "4 0 0 1 20 single 0 \n", + "\n", + " max_agent_points percent_hawk AgentID risk_level choice points \n", + "0 21.0 0.5625 NaN NaN NaN NaN \n", + "1 30.0 0.7600 0.0 0.0 dove 13.5 \n", + "2 30.0 0.7600 1.0 0.0 dove 11.3 \n", + "3 30.0 0.7600 2.0 0.0 dove 13.5 \n", + "4 30.0 0.7600 3.0 0.0 hawk 6.0 " + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "75b9e694-4d87-437a-acc9-97cc233aa542", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
RunIditerationStepgrid_sizerisk_attitudesagent_risk_levelmax_agent_pointspercent_hawkAgentIDrisk_levelchoicepoints
000020single021.00.5625NaNNaNNaNNaN
8000110020single121.00.5150NaNNaNNaNNaN
16000220020single224.00.4800NaNNaNNaNNaN
24000330020single324.00.4575NaNNaNNaNNaN
32000440020single424.00.5300NaNNaNNaNNaN
\n", + "
" + ], + "text/plain": [ + " RunId iteration Step grid_size risk_attitudes agent_risk_level \n", + "0 0 0 0 20 single 0 \\\n", + "80001 1 0 0 20 single 1 \n", + "160002 2 0 0 20 single 2 \n", + "240003 3 0 0 20 single 3 \n", + "320004 4 0 0 20 single 4 \n", + "\n", + " max_agent_points percent_hawk AgentID risk_level choice points \n", + "0 21.0 0.5625 NaN NaN NaN NaN \n", + "80001 21.0 0.5150 NaN NaN NaN NaN \n", + "160002 24.0 0.4800 NaN NaN NaN NaN \n", + "240003 24.0 0.4575 NaN NaN NaN NaN \n", + "320004 24.0 0.5300 NaN NaN NaN NaN " + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model_df = df[df.AgentID.isna()]\n", + "model_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "179b7dae-e0fe-48ea-a201-6e4cb084dab3", + "metadata": {}, + "outputs": [], + "source": [ + "# get model-level data across all rounds and runs\n", + "run_df = df[['RunId', 'iteration', 'Step', 'agent_risk_level', 'percent_hawk']]" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "ef51fd0f-9d68-4d02-bef4-1b4208316b64", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
RunIditerationStepagent_risk_levelpercent_hawk
000000.5625
100100.7600
40100200.9850
80100300.2050
120100400.7600
..................
7180098019680.0000
7184098019780.0000
7188098019880.0000
7192098019980.0000
7196098020080.0000
\n", + "

1809 rows × 5 columns

\n", + "
" + ], + "text/plain": [ + " RunId iteration Step agent_risk_level percent_hawk\n", + "0 0 0 0 0 0.5625\n", + "1 0 0 1 0 0.7600\n", + "401 0 0 2 0 0.9850\n", + "801 0 0 3 0 0.2050\n", + "1201 0 0 4 0 0.7600\n", + "... ... ... ... ... ...\n", + "718009 8 0 196 8 0.0000\n", + "718409 8 0 197 8 0.0000\n", + "718809 8 0 198 8 0.0000\n", + "719209 8 0 199 8 0.0000\n", + "719609 8 0 200 8 0.0000\n", + "\n", + "[1809 rows x 5 columns]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "run_df = run_df.drop_duplicates()\n", + "run_df" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "bf332e64-14a5-46d1-a48f-47b8f01e6980", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import altair as alt\n", + "\n", + "alt.data_transformers.disable_max_rows()\n", + "\n", + "# alt.Chart(run_df[run_df.Step < 100]).mark_line().encode(\n", + "alt.Chart(run_df).mark_line().encode( \n", + " x='Step',\n", + " y='percent_hawk',\n", + " color='agent_risk_level:N',\n", + ").properties(\n", + " width=800,\n", + " height=300\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "8836940d-6c90-4c70-83bb-e3bd65794b61", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
RunIditerationStepagent_risk_levelpercent_hawk
000000.5625
100100.7600
40100200.9850
80100300.2050
120100400.7600
..................
780010019600.7600
784010019700.9850
788010019800.2050
792010019900.7600
796010020000.9850
\n", + "

201 rows × 5 columns

\n", + "
" + ], + "text/plain": [ + " RunId iteration Step agent_risk_level percent_hawk\n", + "0 0 0 0 0 0.5625\n", + "1 0 0 1 0 0.7600\n", + "401 0 0 2 0 0.9850\n", + "801 0 0 3 0 0.2050\n", + "1201 0 0 4 0 0.7600\n", + "... ... ... ... ... ...\n", + "78001 0 0 196 0 0.7600\n", + "78401 0 0 197 0 0.9850\n", + "78801 0 0 198 0 0.2050\n", + "79201 0 0 199 0 0.7600\n", + "79601 0 0 200 0 0.9850\n", + "\n", + "[201 rows x 5 columns]" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "runzero = run_df[run_df.RunId == 0]\n", + "runzero" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "f49d39dd-2fe0-48ce-b44a-55f3ae328a23", + "metadata": {}, + "outputs": [], + "source": [ + "run_one = run_df[run_df.RunId == 1]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "f34b71b5-12d2-49db-83eb-5746f8865527", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "run_zero_chart = alt.Chart(runzero[runzero.Step < 150]).mark_line().encode(\n", + " x='Step', # alt.X('Step', scale=alt.Scale(domain=[0, 1])),\n", + " y='percent_hawk',\n", + " # color='agent_risk_level:N',\n", + ").properties(\n", + " width=800,\n", + " height=300\n", + ")\n", + "run_zero_chart" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "6d2b12e5-3ed0-47a3-a032-a9667306c864", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.LayerChart(...)" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# how to work with this oscillating pattern of alternating hawks?\n", + "# can we use a rolling mean?\n", + "\n", + "line = alt.Chart(runzero).mark_line(\n", + " color='red',\n", + " size=3\n", + ").transform_window(\n", + " rolling_mean='mean(percent_hawk)',\n", + " frame=[-15, 15]\n", + ").encode(\n", + " x='Step',\n", + " y='rolling_mean:Q'\n", + ").properties(\n", + " width=800,\n", + " height=300\n", + ")\n", + "\n", + "points = alt.Chart(runzero).mark_line().encode(\n", + " x='Step',\n", + " y='percent_hawk'\n", + ")\n", + "\n", + "points + line\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "6c1d5323-657f-488b-a00f-b7809e527a4d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.VConcatChart(...)" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# create and display charts for each run / risk level\n", + "\n", + "charts = []\n", + "\n", + "for i in range(8):\n", + " run_i = run_df[run_df.RunId == i]\n", + " risk_level = run_i.agent_risk_level.unique()[0]\n", + " run_chart = alt.Chart(run_i).mark_line().encode(\n", + " x='Step',\n", + " y=alt.Y('percent_hawk', scale=alt.Scale(domain=[0, 1.0]))\n", + " # color='agent_risk_level:N',\n", + " ).properties(\n", + " # title=f'Run {i}, risk level {risk_level}',\n", + " title=f'Risk level {risk_level}',\n", + " width=600,\n", + " height=90\n", + " )\n", + " charts.append(run_chart)\n", + "\n", + "combined_chart = None\n", + "for c in charts:\n", + " if combined_chart is None:\n", + " combined_chart = c\n", + " else:\n", + " combined_chart = alt.vconcat(combined_chart, c)\n", + "\n", + "combined_chart" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "d101de79-9dbc-4581-8d9e-82157543cd14", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.VConcatChart(...)" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# do the same thing, but display beginning instead of end and add the rolling mean\n", + "\n", + "rollmean_charts = []\n", + "\n", + "for i in range(8):\n", + " run_i = run_df[run_df.RunId == i]\n", + " risk_level = run_i.agent_risk_level.unique()[0]\n", + " run_chart = alt.Chart(run_i).mark_line().encode(\n", + " x='Step',\n", + " y=alt.Y('percent_hawk', scale=alt.Scale(domain=[0, 1.0]))\n", + " # color='agent_risk_level:N',\n", + " ).properties(\n", + " # title=f'Run {i}, risk level {risk_level}',\n", + " title=f'Risk level {risk_level}',\n", + " width=800,\n", + " height=90\n", + " )\n", + " rollmean_line = alt.Chart(runzero[runzero.Step < 300]).mark_line(\n", + " color='red',\n", + " size=3\n", + " ).transform_window(\n", + " rolling_mean='mean(percent_hawk)',\n", + " frame=[-15, 15]\n", + " ).encode(\n", + " x='Step',\n", + " y='rolling_mean:Q'\n", + " )\n", + " \n", + " rollmean_charts.append(run_chart + rollmean_line)\n", + "\n", + "rollmean_combined_chart = None\n", + "for c in rollmean_charts:\n", + " if rollmean_combined_chart is None:\n", + " rollmean_combined_chart = c\n", + " else:\n", + " rollmean_combined_chart = alt.vconcat(rollmean_combined_chart, c)\n", + "\n", + "rollmean_combined_chart" + ] + }, + { + "cell_type": "markdown", + "id": "5ee36078-5c36-4d43-a825-7eb82b2bac6e", + "metadata": {}, + "source": [ + "## percent hawk by risk level" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "02e2d729-1618-4584-a488-51cba61ab58e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
risk_levelmaxmeanmin
000.98500.6517790.2050
110.99000.5890800.2200
221.00000.5510450.2825
330.97250.5144900.1700
440.81750.4853480.0200
550.68000.4423630.0125
660.78500.4101490.0025
770.82250.3541920.0025
\n", + "
" + ], + "text/plain": [ + " risk_level max mean min\n", + "0 0 0.9850 0.651779 0.2050\n", + "1 1 0.9900 0.589080 0.2200\n", + "2 2 1.0000 0.551045 0.2825\n", + "3 3 0.9725 0.514490 0.1700\n", + "4 4 0.8175 0.485348 0.0200\n", + "5 5 0.6800 0.442363 0.0125\n", + "6 6 0.7850 0.410149 0.0025\n", + "7 7 0.8225 0.354192 0.0025" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# for each run (= risk level), what are upper and lower values and rolling mean for % hawk?\n", + "\n", + "hawkstats = []\n", + "\n", + "for i in range(8) :\n", + " # get the end of the run; we stopped at 1000 but it stabilized very early; use last 50 rounds\n", + " # in this run, we only ran for 200 iterations, since it stabilizes quickly\n", + " run_i = run_df[run_df.RunId == i]\n", + " phawk_vals = run_i.percent_hawk.describe()\n", + " # add one entry for each value with a type, so we can graph all at once in altair with a legend\n", + " hawkstats.append({\n", + " 'risk_level': i, \n", + " 'max': run_i.percent_hawk.max(), \n", + " 'mean': run_i.percent_hawk.mean(), \n", + " 'min': run_i.percent_hawk.min()\n", + " })\n", + "\n", + "hawkstats_df = pd.DataFrame(hawkstats)\n", + "hawkstats_df" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "d4d2487c-4382-442b-ba19-9ffce336a237", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
risk_levelvaluetype
000.985000max
100.650000mean
200.205000min
310.990000max
410.587500mean
510.222500min
621.000000max
720.550000mean
820.282500min
930.972500max
1030.514167mean
1130.172500min
1240.802500max
1340.485833mean
1440.025000min
1550.680000max
1650.443333mean
1750.012500min
1860.785000max
1960.411667mean
2060.005000min
2170.822500max
2270.355833mean
2370.005000min
\n", + "
" + ], + "text/plain": [ + " risk_level value type\n", + "0 0 0.985000 max\n", + "1 0 0.650000 mean\n", + "2 0 0.205000 min\n", + "3 1 0.990000 max\n", + "4 1 0.587500 mean\n", + "5 1 0.222500 min\n", + "6 2 1.000000 max\n", + "7 2 0.550000 mean\n", + "8 2 0.282500 min\n", + "9 3 0.972500 max\n", + "10 3 0.514167 mean\n", + "11 3 0.172500 min\n", + "12 4 0.802500 max\n", + "13 4 0.485833 mean\n", + "14 4 0.025000 min\n", + "15 5 0.680000 max\n", + "16 5 0.443333 mean\n", + "17 5 0.012500 min\n", + "18 6 0.785000 max\n", + "19 6 0.411667 mean\n", + "20 6 0.005000 min\n", + "21 7 0.822500 max\n", + "22 7 0.355833 mean\n", + "23 7 0.005000 min" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# for each run (= risk level), what are upper and lower values and rolling mean for % hawk?\n", + "# format in a way we can easily graph together with altair\n", + "\n", + "alt_hawkstats = []\n", + "\n", + "for i in range(8) :\n", + " # get the end of the run; we stopped at 1000 but it stabilized very early; use last 50 rounds\n", + " # ran for 200 rounds; omit first 50 before it stabilized\n", + " run_i = run_df[(run_df.RunId == i) & (run_df.Step > 50)]\n", + " phawk_vals = run_i.percent_hawk.describe()\n", + " # add one entry for each value with a type, so we can graph all at once in altair with a legend\n", + " alt_hawkstats.extend([\n", + " {'risk_level': i, 'value': run_i.percent_hawk.max(), 'type': 'max'},\n", + " {'risk_level': i, 'value': run_i.percent_hawk.mean(), 'type': 'mean'},\n", + " {'risk_level': i, 'value': run_i.percent_hawk.min(), 'type': 'min'},\n", + " ])\n", + "\n", + "alt_hawkstats_df = pd.DataFrame(alt_hawkstats)\n", + "alt_hawkstats_df" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "aa90ddfa-5a8d-4ca8-b4ca-219331a3d575", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "alt.Chart(alt_hawkstats_df).mark_line().encode(\n", + " x='risk_level:N', \n", + " y='value',\n", + " color=alt.Color('type').scale(domain=['min', 'mean', 'max'], range=['purple', 'blue', 'orange'])\n", + ").properties(\n", + " title='% hawk by risk level (min, max, mean)',\n", + " width=500,\n", + " height=400\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "f49d27f7-6a44-4158-94bf-80771993c990", + "metadata": {}, + "source": [ + "## points by risk level" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "f4a71d04-a192-4ec0-8072-6b3aee9097eb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
RunIdpoints
001527.5400
111797.3435
221956.5375
332043.0240
442150.2400
552288.3545
662417.1090
772492.1570
883355.4040
\n", + "
" + ], + "text/plain": [ + " RunId points\n", + "0 0 1527.5400\n", + "1 1 1797.3435\n", + "2 2 1956.5375\n", + "3 3 2043.0240\n", + "4 4 2150.2400\n", + "5 5 2288.3545\n", + "6 6 2417.1090\n", + "7 7 2492.1570\n", + "8 8 3355.4040" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# what about points?\n", + "\n", + "# get points at the last round only, so we're looking at the end state\n", + "\n", + "last_round_n = df.Step.max()\n", + "\n", + "last_round = df[df.Step == last_round_n]\n", + "\n", + "points_mean = last_round.groupby('RunId', as_index=False).aggregate('points').mean() # : ['mean', 'sum']})\n", + "points_mean" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "1cd28be4-b25f-45a4-a6ed-6ed77e0b15a7", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\n", + "alt.Chart(points_mean).mark_bar().encode(\n", + " x=alt.Y('RunId:N', title='risk level'),\n", + " y=alt.Y('points', title='average points'),\n", + ").properties(\n", + " title='average points by risk level',\n", + " width=500,\n", + " height=400\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "cc34f2fe-da99-4a74-8857-90217bde4208", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
RunIdpointstype
001527.5400mean
111797.3435mean
221956.5375mean
332043.0240mean
442150.2400mean
552288.3545mean
662417.1090mean
772492.1570mean
883355.4040mean
00975.6000min
111132.5000min
221454.4000min
331658.3000min
441809.0000min
551810.8000min
661954.9000min
771956.0000min
883343.2000min
002285.4000max
112411.0000max
222574.6000max
332657.7000max
442697.3000max
552768.4000max
662926.6000max
773723.6000max
883364.2000max
\n", + "
" + ], + "text/plain": [ + " RunId points type\n", + "0 0 1527.5400 mean\n", + "1 1 1797.3435 mean\n", + "2 2 1956.5375 mean\n", + "3 3 2043.0240 mean\n", + "4 4 2150.2400 mean\n", + "5 5 2288.3545 mean\n", + "6 6 2417.1090 mean\n", + "7 7 2492.1570 mean\n", + "8 8 3355.4040 mean\n", + "0 0 975.6000 min\n", + "1 1 1132.5000 min\n", + "2 2 1454.4000 min\n", + "3 3 1658.3000 min\n", + "4 4 1809.0000 min\n", + "5 5 1810.8000 min\n", + "6 6 1954.9000 min\n", + "7 7 1956.0000 min\n", + "8 8 3343.2000 min\n", + "0 0 2285.4000 max\n", + "1 1 2411.0000 max\n", + "2 2 2574.6000 max\n", + "3 3 2657.7000 max\n", + "4 4 2697.3000 max\n", + "5 5 2768.4000 max\n", + "6 6 2926.6000 max\n", + "7 7 3723.6000 max\n", + "8 8 3364.2000 max" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# what about min/max?\n", + "\n", + "# aggregrate separately so we can graph together in altair\n", + "points_mean['type'] = 'mean'\n", + "\n", + "points_min = last_round.groupby('RunId', as_index=False).aggregate('points').min()\n", + "points_min['type'] = 'min'\n", + "\n", + "points_max = last_round.groupby('RunId', as_index=False).aggregate('points').max()\n", + "points_max['type'] = 'max'\n", + "\n", + "points_combined = pd.concat([points_mean, points_min, points_max])\n", + "\n", + "points_combined" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "fdc602ac-73cd-4952-8a6b-613fab6e9ab8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "alt.Chart(points_combined).mark_line().encode(\n", + " x=alt.Y('RunId:N', title='risk level'),\n", + " y=alt.Y('points', title='average points'),\n", + " color='type'\n", + ").properties(\n", + " title='points by risk level',\n", + " width=500,\n", + " height=400\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "6324e289-f07a-49f0-ab72-09e00f1ca72b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
RunIditerationStepgrid_sizerisk_attitudesagent_risk_levelmax_agent_pointspercent_hawkAgentIDrisk_levelchoicepoints
796010020020single02285.40.9850.00.0dove1658.3
796020020020single02285.40.9851.00.0dove1656.1
796030020020single02285.40.9852.00.0dove1585.7
796040020020single02285.40.9853.00.0dove1679.4
796050020020single02285.40.9854.00.0dove1909.5
.......................................
7200048020020single83381.00.000395.08.0dove3355.2
7200058020020single83381.00.000396.08.0dove3352.2
7200068020020single83381.00.000397.08.0dove3355.2
7200078020020single83381.00.000398.08.0dove3355.2
7200088020020single83381.00.000399.08.0dove3352.2
\n", + "

3600 rows × 12 columns

\n", + "
" + ], + "text/plain": [ + " RunId iteration Step grid_size risk_attitudes agent_risk_level \n", + "79601 0 0 200 20 single 0 \\\n", + "79602 0 0 200 20 single 0 \n", + "79603 0 0 200 20 single 0 \n", + "79604 0 0 200 20 single 0 \n", + "79605 0 0 200 20 single 0 \n", + "... ... ... ... ... ... ... \n", + "720004 8 0 200 20 single 8 \n", + "720005 8 0 200 20 single 8 \n", + "720006 8 0 200 20 single 8 \n", + "720007 8 0 200 20 single 8 \n", + "720008 8 0 200 20 single 8 \n", + "\n", + " max_agent_points percent_hawk AgentID risk_level choice points \n", + "79601 2285.4 0.985 0.0 0.0 dove 1658.3 \n", + "79602 2285.4 0.985 1.0 0.0 dove 1656.1 \n", + "79603 2285.4 0.985 2.0 0.0 dove 1585.7 \n", + "79604 2285.4 0.985 3.0 0.0 dove 1679.4 \n", + "79605 2285.4 0.985 4.0 0.0 dove 1909.5 \n", + "... ... ... ... ... ... ... \n", + "720004 3381.0 0.000 395.0 8.0 dove 3355.2 \n", + "720005 3381.0 0.000 396.0 8.0 dove 3352.2 \n", + "720006 3381.0 0.000 397.0 8.0 dove 3355.2 \n", + "720007 3381.0 0.000 398.0 8.0 dove 3355.2 \n", + "720008 3381.0 0.000 399.0 8.0 dove 3352.2 \n", + "\n", + "[3600 rows x 12 columns]" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "last_round" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "6c4a20b4-939e-4aad-b2e4-018bf988618a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "alt.Chart(last_round).mark_boxplot(extent=\"min-max\").encode(\n", + " alt.X(\"points:Q\").scale(zero=False),\n", + " alt.Y(\"agent_risk_level:N\", title=\"risk level\"),\n", + ").properties(\n", + " title='range of points per agent by risk level',\n", + " width=500,\n", + " height=400\n", + ")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "ed2c5c3d-bcdc-496a-9219-2bd02d5704df", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
risk_levelmaxmeanmin
002285.41527.5400975.6
112411.01797.34351132.5
222574.61956.53751454.4
332657.72043.02401658.3
442697.32150.24001809.0
552768.42288.35451810.8
662926.62417.10901954.9
773723.62492.15701956.0
\n", + "
" + ], + "text/plain": [ + " risk_level max mean min\n", + "0 0 2285.4 1527.5400 975.6\n", + "1 1 2411.0 1797.3435 1132.5\n", + "2 2 2574.6 1956.5375 1454.4\n", + "3 3 2657.7 2043.0240 1658.3\n", + "4 4 2697.3 2150.2400 1809.0\n", + "5 5 2768.4 2288.3545 1810.8\n", + "6 6 2926.6 2417.1090 1954.9\n", + "7 7 3723.6 2492.1570 1956.0" + ] + }, + "execution_count": 34, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# for each run (= risk level), what are upper and lower values for individual points?\n", + "\n", + "points = []\n", + "\n", + "for i in range(8) :\n", + " run_i = last_round[last_round.RunId == i]\n", + " # add one entry for each value with a type, so we can graph all at once in altair with a legend\n", + " points.append({\n", + " 'risk_level': i, \n", + " 'max': run_i.points.max(), \n", + " 'mean': run_i.points.mean(), \n", + " 'min': run_i.points.min()\n", + " })\n", + "\n", + "points_df = pd.DataFrame(points)\n", + "points_df" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/notebooks/hawkdove_variable_r_analysis.ipynb b/notebooks/hawkdove_variable_r_analysis.ipynb new file mode 100644 index 0000000..ace1194 --- /dev/null +++ b/notebooks/hawkdove_variable_r_analysis.ipynb @@ -0,0 +1,3037 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 2, + "id": "60d1f875-01f6-41ea-9e9d-385271fba925", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "id": "436fe77f-c9a6-4bf2-a395-b496f4a35a59", + "metadata": {}, + "outputs": [], + "source": [ + "# df = pd.read_csv(\"../hawkdove_risk-variable_2023-09-26T115400_410252.csv\")\n", + "\n", + "# updated batch run csv that includes agent points and only goes for 200 iterations\n", + "df = pd.read_csv(\"../hawkdove_risk-variable_2023-09-27T171431_729869.csv\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "id": "bb714902-b6dd-4bdd-9640-df0df52979d9", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
RunIditerationStepgrid_sizerisk_attitudesmax_agent_pointspercent_hawkAgentIDrisk_levelchoicepoints
000020variable24.00.4975NaNNaNNaNNaN
100120variable42.00.21500.08.0dove10.2
200120variable42.00.21501.08.0hawk9.0
300120variable42.00.21502.08.0dove13.5
400120variable42.00.21503.06.0hawk12.0
\n", + "
" + ], + "text/plain": [ + " RunId iteration Step grid_size risk_attitudes max_agent_points \n", + "0 0 0 0 20 variable 24.0 \\\n", + "1 0 0 1 20 variable 42.0 \n", + "2 0 0 1 20 variable 42.0 \n", + "3 0 0 1 20 variable 42.0 \n", + "4 0 0 1 20 variable 42.0 \n", + "\n", + " percent_hawk AgentID risk_level choice points \n", + "0 0.4975 NaN NaN NaN NaN \n", + "1 0.2150 0.0 8.0 dove 10.2 \n", + "2 0.2150 1.0 8.0 hawk 9.0 \n", + "3 0.2150 2.0 8.0 dove 13.5 \n", + "4 0.2150 3.0 6.0 hawk 12.0 " + ] + }, + "execution_count": 74, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "id": "75b9e694-4d87-437a-acc9-97cc233aa542", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
RunIditerationStepgrid_sizerisk_attitudesmax_agent_pointspercent_hawkAgentIDrisk_levelchoicepoints
000020variable24.00.4975NaNNaNNaNNaN
8000111020variable21.00.5150NaNNaNNaNNaN
16000222020variable21.00.5200NaNNaNNaNNaN
24000333020variable21.00.5400NaNNaNNaNNaN
32000444020variable24.00.4775NaNNaNNaNNaN
\n", + "
" + ], + "text/plain": [ + " RunId iteration Step grid_size risk_attitudes max_agent_points \n", + "0 0 0 0 20 variable 24.0 \\\n", + "80001 1 1 0 20 variable 21.0 \n", + "160002 2 2 0 20 variable 21.0 \n", + "240003 3 3 0 20 variable 21.0 \n", + "320004 4 4 0 20 variable 24.0 \n", + "\n", + " percent_hawk AgentID risk_level choice points \n", + "0 0.4975 NaN NaN NaN NaN \n", + "80001 0.5150 NaN NaN NaN NaN \n", + "160002 0.5200 NaN NaN NaN NaN \n", + "240003 0.5400 NaN NaN NaN NaN \n", + "320004 0.4775 NaN NaN NaN NaN " + ] + }, + "execution_count": 75, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# can we filter model data from agent data based on presence of agent id?\n", + "model_df = df[df.AgentID.isna()]\n", + "model_df.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "id": "179b7dae-e0fe-48ea-a201-6e4cb084dab3", + "metadata": {}, + "outputs": [], + "source": [ + "# in variable risk mode we don't have agent risk level; individual agents report their risk level\n", + "run_df = df[['RunId', 'iteration', 'Step', 'percent_hawk']]" + ] + }, + { + "cell_type": "raw", + "id": "2263d4d7-71dc-410b-8e18-32b64b889099", + "metadata": {}, + "source": [] + }, + { + "cell_type": "code", + "execution_count": 77, + "id": "ef51fd0f-9d68-4d02-bef4-1b4208316b64", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
RunIditerationSteppercent_hawk
00000.4975
10010.2150
4010020.6175
8010030.5175
12010040.3300
...............
398005441960.3775
398405441970.5450
398805441980.5050
399205441990.3775
399605442000.5450
\n", + "

1005 rows × 4 columns

\n", + "
" + ], + "text/plain": [ + " RunId iteration Step percent_hawk\n", + "0 0 0 0 0.4975\n", + "1 0 0 1 0.2150\n", + "401 0 0 2 0.6175\n", + "801 0 0 3 0.5175\n", + "1201 0 0 4 0.3300\n", + "... ... ... ... ...\n", + "398005 4 4 196 0.3775\n", + "398405 4 4 197 0.5450\n", + "398805 4 4 198 0.5050\n", + "399205 4 4 199 0.3775\n", + "399605 4 4 200 0.5450\n", + "\n", + "[1005 rows x 4 columns]" + ] + }, + "execution_count": 77, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "run_df = run_df.drop_duplicates()\n", + "run_df" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "id": "bf332e64-14a5-46d1-a48f-47b8f01e6980", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 79, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import altair as alt\n", + "\n", + "alt.data_transformers.disable_max_rows()\n", + "\n", + "alt.Chart(run_df).mark_line().encode(\n", + " x='Step',\n", + " y='percent_hawk',\n", + " color='RunId:N',\n", + ").properties(\n", + " width=800,\n", + " height=300\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "id": "8836940d-6c90-4c70-83bb-e3bd65794b61", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
RunIditerationSteppercent_hawk
00000.4975
10010.2150
4010020.6175
8010030.5175
12010040.3300
...............
78001001960.3775
78401001970.5325
78801001980.5125
79201001990.3775
79601002000.5325
\n", + "

201 rows × 4 columns

\n", + "
" + ], + "text/plain": [ + " RunId iteration Step percent_hawk\n", + "0 0 0 0 0.4975\n", + "1 0 0 1 0.2150\n", + "401 0 0 2 0.6175\n", + "801 0 0 3 0.5175\n", + "1201 0 0 4 0.3300\n", + "... ... ... ... ...\n", + "78001 0 0 196 0.3775\n", + "78401 0 0 197 0.5325\n", + "78801 0 0 198 0.5125\n", + "79201 0 0 199 0.3775\n", + "79601 0 0 200 0.5325\n", + "\n", + "[201 rows x 4 columns]" + ] + }, + "execution_count": 80, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "runzero = run_df[run_df.RunId == 0]\n", + "runzero" + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "id": "f49d39dd-2fe0-48ce-b44a-55f3ae328a23", + "metadata": {}, + "outputs": [], + "source": [ + "run_one = run_df[run_df.RunId == 1]\n" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "id": "f34b71b5-12d2-49db-83eb-5746f8865527", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 83, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "run_zero_chart = alt.Chart(runzero).mark_line().encode(\n", + " x='Step', # alt.X('Step', scale=alt.Scale(domain=[0, 1])),\n", + " y='percent_hawk',\n", + " # color='agent_risk_level:N',\n", + ").properties(\n", + " width=800,\n", + " height=300\n", + ")\n", + "run_zero_chart" + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "id": "6d2b12e5-3ed0-47a3-a032-a9667306c864", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.LayerChart(...)" + ] + }, + "execution_count": 84, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# how to work with this oscillating pattern of alternating hawks?\n", + "# can we use a rolling mean?\n", + "\n", + "line = alt.Chart(runzero[runzero.Step < 300]).mark_line(\n", + " color='red',\n", + " size=3\n", + ").transform_window(\n", + " rolling_mean='mean(percent_hawk)',\n", + " frame=[-15, 15]\n", + ").encode(\n", + " x='Step',\n", + " y='rolling_mean:Q'\n", + ").properties(\n", + " width=800,\n", + " height=300\n", + ")\n", + "\n", + "points = alt.Chart(runzero[runzero.Step < 300]).mark_line().encode(\n", + " x='Step',\n", + " y='percent_hawk'\n", + ")\n", + "\n", + "points + line\n" + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "id": "6c1d5323-657f-488b-a00f-b7809e527a4d", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.VConcatChart(...)" + ] + }, + "execution_count": 87, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# create and display charts for each run - should be about the same, since same starting conditions\n", + "\n", + "# this stabilizes quickly, so only display first 200 rounds\n", + "\n", + "charts = []\n", + "\n", + "total_runs = len(run_df.RunId.unique())\n", + "\n", + "for i in range(total_runs):\n", + " run_i = run_df[(run_df.RunId == i) & (run_df.Step < 200)]\n", + " run_chart = alt.Chart(run_i).mark_line().encode(\n", + " x='Step',\n", + " y=alt.Y('percent_hawk', scale=alt.Scale(domain=[0, 1.0]))\n", + " # color='agent_risk_level:N',\n", + " ).properties(\n", + " title=f'Run {i}',\n", + " width=800,\n", + " height=90\n", + " )\n", + " charts.append(run_chart)\n", + "\n", + "combined_chart = None\n", + "for c in charts:\n", + " if combined_chart is None:\n", + " combined_chart = c\n", + " else:\n", + " combined_chart = alt.vconcat(combined_chart, c)\n", + "\n", + "combined_chart" + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "id": "d101de79-9dbc-4581-8d9e-82157543cd14", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.VConcatChart(...)" + ] + }, + "execution_count": 88, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# do the same thing, but display beginning instead of end and add the rolling mean\n", + "\n", + "rollmean_charts = []\n", + "\n", + "for i in range(total_runs):\n", + " run_i = run_df[run_df.RunId == i]\n", + " run_chart = alt.Chart(run_i).mark_line().encode(\n", + " x='Step',\n", + " y=alt.Y('percent_hawk', scale=alt.Scale(domain=[0, 1.0]))\n", + " # color='agent_risk_level:N',\n", + " ).properties(\n", + " title=f'Run {i}',\n", + " width=800,\n", + " height=90\n", + " )\n", + " # graph the rolling mean\n", + " rollmean_line = alt.Chart(run_i).mark_line(\n", + " color='red',\n", + " size=3\n", + " ).transform_window(\n", + " rolling_mean='mean(percent_hawk)',\n", + " frame=[-15, 15]\n", + " ).encode(\n", + " x='Step',\n", + " y='rolling_mean:Q'\n", + " # ).properties(\n", + " # width=800,\n", + " # height=300\n", + " )\n", + " \n", + " rollmean_charts.append(run_chart + rollmean_line)\n", + "\n", + "rollmean_combined_chart = None\n", + "for c in rollmean_charts:\n", + " if rollmean_combined_chart is None:\n", + " rollmean_combined_chart = c\n", + " else:\n", + " rollmean_combined_chart = alt.vconcat(rollmean_combined_chart, c)\n", + "\n", + "rollmean_combined_chart" + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "id": "095250b5-f688-4127-bab5-f48f761827cc", + "metadata": {}, + "outputs": [], + "source": [ + "last_step = run_df.Step.max()" + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "id": "bde9a45e-6424-4f18-908a-73a3328d1ac6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
RunIditerationStepgrid_sizerisk_attitudesmax_agent_pointspercent_hawkAgentIDrisk_levelchoicepoints
796010020020variable4210.80.53250.08.0dove2406.3
796020020020variable4210.80.53251.08.0dove2185.1
796030020020variable4210.80.53252.08.0dove2121.4
796040020020variable4210.80.53253.06.0dove2119.9
796050020020variable4210.80.53254.07.0dove2266.6
\n", + "
" + ], + "text/plain": [ + " RunId iteration Step grid_size risk_attitudes max_agent_points \n", + "79601 0 0 200 20 variable 4210.8 \\\n", + "79602 0 0 200 20 variable 4210.8 \n", + "79603 0 0 200 20 variable 4210.8 \n", + "79604 0 0 200 20 variable 4210.8 \n", + "79605 0 0 200 20 variable 4210.8 \n", + "\n", + " percent_hawk AgentID risk_level choice points \n", + "79601 0.5325 0.0 8.0 dove 2406.3 \n", + "79602 0.5325 1.0 8.0 dove 2185.1 \n", + "79603 0.5325 2.0 8.0 dove 2121.4 \n", + "79604 0.5325 3.0 6.0 dove 2119.9 \n", + "79605 0.5325 4.0 7.0 dove 2266.6 " + ] + }, + "execution_count": 90, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df[df.Step == last_step].head()" + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "id": "7a943c93-0025-45cb-b11c-6c5cb88d2b96", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Steprisk_level
118.0
416.0
517.0
611.0
1113.0
\n", + "
" + ], + "text/plain": [ + " Step risk_level\n", + "1 1 8.0\n", + "4 1 6.0\n", + "5 1 7.0\n", + "6 1 1.0\n", + "11 1 3.0" + ] + }, + "execution_count": 91, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# we want to calculate percent hawk by risk level per round\n", + "\n", + "# create a minimal df with step, and risk level\n", + "# NOTE: collapsing all runs together\n", + "phawk_by_risk = df[df.risk_level.notna()][['Step', 'risk_level']].drop_duplicates().copy()\n", + "phawk_by_risk.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 92, + "id": "477691e8-670b-497c-8216-bc9b7a1f64e6", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Steprisk_level
118.0
416.0
517.0
611.0
1113.0
.........
796112003.0
796142002.0
796172004.0
796212000.0
796252005.0
\n", + "

1800 rows × 2 columns

\n", + "
" + ], + "text/plain": [ + " Step risk_level\n", + "1 1 8.0\n", + "4 1 6.0\n", + "5 1 7.0\n", + "6 1 1.0\n", + "11 1 3.0\n", + "... ... ...\n", + "79611 200 3.0\n", + "79614 200 2.0\n", + "79617 200 4.0\n", + "79621 200 0.0\n", + "79625 200 5.0\n", + "\n", + "[1800 rows x 2 columns]" + ] + }, + "execution_count": 92, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "phawk_by_risk" + ] + }, + { + "cell_type": "code", + "execution_count": 93, + "id": "57e57859-d692-466a-b7bb-64aca4d8b02e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Steprisk_levelpercent_hawk
118.00.486726
416.00.497797
517.00.546256
611.00.461905
1113.00.518182
1412.00.557851
1714.00.553488
2110.00.476415
2515.00.484163
40128.00.000000
\n", + "
" + ], + "text/plain": [ + " Step risk_level percent_hawk\n", + "1 1 8.0 0.486726\n", + "4 1 6.0 0.497797\n", + "5 1 7.0 0.546256\n", + "6 1 1.0 0.461905\n", + "11 1 3.0 0.518182\n", + "14 1 2.0 0.557851\n", + "17 1 4.0 0.553488\n", + "21 1 0.0 0.476415\n", + "25 1 5.0 0.484163\n", + "401 2 8.0 0.000000" + ] + }, + "execution_count": 93, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def calculate_percent_hawk(step):\n", + " # step_df = df[(df.risk_level == step.risk_level) & (df.RunId == step.RunId) & (df.Step == step.Step)]\n", + " step_df = df[(df.risk_level == step.risk_level) & (df.Step == step.Step)]\n", + " # in at least some cases, hawk is not present which results in an attribute error; assume 0% hawks\n", + " try:\n", + " # number of hawks / total number of agents\n", + " return step_df.choice.value_counts().hawk / len(step_df.choice)\n", + " except AttributeError:\n", + " return 0\n", + "\n", + "phawk_by_risk['percent_hawk'] = phawk_by_risk.apply(lambda row: calculate_percent_hawk(row), axis=1)\n", + "phawk_by_risk.head(10)" + ] + }, + { + "cell_type": "code", + "execution_count": 94, + "id": "369b0191-58fd-4e14-9f77-6ad43ec70335", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 94, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "alt.Chart(phawk_by_risk).mark_line().encode(\n", + " x='Step:Q',\n", + " y=alt.Y('percent_hawk', scale=alt.Scale(domain=[0, 1.0])),\n", + " color='risk_level:N',\n", + ").properties(\n", + " title=f'Percent hawk by risk level',\n", + " width=800,\n", + " height=500\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 95, + "id": "5889782e-4012-4cb7-9d96-f582f7e799b1", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Steprisk_levelpercent_hawkrolling_percent_hawk
118.00.486726NaN
416.00.497797NaN
517.00.546256NaN
611.00.461905NaN
1113.00.518182NaN
1412.00.557851NaN
1714.00.553488NaN
2110.00.476415NaN
2515.00.484163NaN
40128.00.0000000.458278
\n", + "
" + ], + "text/plain": [ + " Step risk_level percent_hawk rolling_percent_hawk\n", + "1 1 8.0 0.486726 NaN\n", + "4 1 6.0 0.497797 NaN\n", + "5 1 7.0 0.546256 NaN\n", + "6 1 1.0 0.461905 NaN\n", + "11 1 3.0 0.518182 NaN\n", + "14 1 2.0 0.557851 NaN\n", + "17 1 4.0 0.553488 NaN\n", + "21 1 0.0 0.476415 NaN\n", + "25 1 5.0 0.484163 NaN\n", + "401 2 8.0 0.000000 0.458278" + ] + }, + "execution_count": 95, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# calculate a rolling average\n", + "phawk_by_risk['rolling_percent_hawk'] = phawk_by_risk.percent_hawk.rolling(window=10).mean()\n", + "phawk_by_risk.head(10)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 96, + "id": "c3d8a970-3b5a-4eb6-9297-9c73cdcb982e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 96, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "alt.Chart(phawk_by_risk).mark_line().encode(\n", + " x='Step:Q',\n", + " y=alt.Y('rolling_percent_hawk', scale=alt.Scale(domain=[0, 1.0])),\n", + " color='risk_level:N',\n", + ").properties(\n", + " title=f'Rolling average Percent hawk by risk level',\n", + " width=800,\n", + " height=500\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 97, + "id": "d485cfc9-2fe4-452e-ac71-c00f641ac4e3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
RunIditerationStepgrid_sizerisk_attitudesmax_agent_pointspercent_hawkAgentIDrisk_levelchoicepoints
796010020020variable4210.80.53250.08.0dove2406.3
796020020020variable4210.80.53251.08.0dove2185.1
796030020020variable4210.80.53252.08.0dove2121.4
796040020020variable4210.80.53253.06.0dove2119.9
796050020020variable4210.80.53254.07.0dove2266.6
....................................
4000004420020variable4809.00.5450395.07.0dove2265.5
4000014420020variable4809.00.5450396.07.0dove2334.8
4000024420020variable4809.00.5450397.08.0dove2773.7
4000034420020variable4809.00.5450398.01.0hawk2403.4
4000044420020variable4809.00.5450399.06.0dove2703.3
\n", + "

2000 rows × 11 columns

\n", + "
" + ], + "text/plain": [ + " RunId iteration Step grid_size risk_attitudes max_agent_points \n", + "79601 0 0 200 20 variable 4210.8 \\\n", + "79602 0 0 200 20 variable 4210.8 \n", + "79603 0 0 200 20 variable 4210.8 \n", + "79604 0 0 200 20 variable 4210.8 \n", + "79605 0 0 200 20 variable 4210.8 \n", + "... ... ... ... ... ... ... \n", + "400000 4 4 200 20 variable 4809.0 \n", + "400001 4 4 200 20 variable 4809.0 \n", + "400002 4 4 200 20 variable 4809.0 \n", + "400003 4 4 200 20 variable 4809.0 \n", + "400004 4 4 200 20 variable 4809.0 \n", + "\n", + " percent_hawk AgentID risk_level choice points \n", + "79601 0.5325 0.0 8.0 dove 2406.3 \n", + "79602 0.5325 1.0 8.0 dove 2185.1 \n", + "79603 0.5325 2.0 8.0 dove 2121.4 \n", + "79604 0.5325 3.0 6.0 dove 2119.9 \n", + "79605 0.5325 4.0 7.0 dove 2266.6 \n", + "... ... ... ... ... ... \n", + "400000 0.5450 395.0 7.0 dove 2265.5 \n", + "400001 0.5450 396.0 7.0 dove 2334.8 \n", + "400002 0.5450 397.0 8.0 dove 2773.7 \n", + "400003 0.5450 398.0 1.0 hawk 2403.4 \n", + "400004 0.5450 399.0 6.0 dove 2703.3 \n", + "\n", + "[2000 rows x 11 columns]" + ] + }, + "execution_count": 97, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# what about points?\n", + "\n", + "# get points at the last round only, so we're looking at the end state\n", + "\n", + "last_round_n = df.Step.max()\n", + "\n", + "last_round = df[df.Step == last_round_n]\n", + "last_round" + ] + }, + { + "cell_type": "code", + "execution_count": 98, + "id": "5005eba0-0c87-4fb3-aa13-12b2b0f1f5df", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
risk_levelpoints
00.02818.093396
11.02785.355238
22.02705.011983
33.02647.818636
44.02583.976744
55.02486.330769
66.02454.149780
77.02464.065639
88.02444.124779
\n", + "
" + ], + "text/plain": [ + " risk_level points\n", + "0 0.0 2818.093396\n", + "1 1.0 2785.355238\n", + "2 2.0 2705.011983\n", + "3 3.0 2647.818636\n", + "4 4.0 2583.976744\n", + "5 5.0 2486.330769\n", + "6 6.0 2454.149780\n", + "7 7.0 2464.065639\n", + "8 8.0 2444.124779" + ] + }, + "execution_count": 98, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\n", + "points_mean = last_round.groupby('risk_level', as_index=False).aggregate('points').mean() # : ['mean', 'sum']})\n", + "points_mean" + ] + }, + { + "cell_type": "code", + "execution_count": 99, + "id": "35a96cc8-aced-4bdf-89fc-72bf6b1f0a59", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 99, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "alt.Chart(points_mean).mark_bar().encode(\n", + " x=alt.Y('risk_level:N', title='risk level'),\n", + " y=alt.Y('points', title='average points'),\n", + ").properties(\n", + " title='average points by risk level',\n", + " width=500,\n", + " height=400\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 100, + "id": "52609e6e-316a-481b-9e01-c500db3e883a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
risk_levelpointstype
00.02818.093396mean
11.02785.355238mean
22.02705.011983mean
33.02647.818636mean
44.02583.976744mean
55.02486.330769mean
66.02454.149780mean
77.02464.065639mean
88.02444.124779mean
00.01212.000000min
11.01417.500000min
22.01643.600000min
33.01590.500000min
44.01786.200000min
55.01664.900000min
66.02037.800000min
77.01825.100000min
88.01823.300000min
00.04182.000000max
11.04187.600000max
22.04785.000000max
33.04189.500000max
44.04384.400000max
55.03976.400000max
66.03699.600000max
77.03361.800000max
88.02994.900000max
\n", + "
" + ], + "text/plain": [ + " risk_level points type\n", + "0 0.0 2818.093396 mean\n", + "1 1.0 2785.355238 mean\n", + "2 2.0 2705.011983 mean\n", + "3 3.0 2647.818636 mean\n", + "4 4.0 2583.976744 mean\n", + "5 5.0 2486.330769 mean\n", + "6 6.0 2454.149780 mean\n", + "7 7.0 2464.065639 mean\n", + "8 8.0 2444.124779 mean\n", + "0 0.0 1212.000000 min\n", + "1 1.0 1417.500000 min\n", + "2 2.0 1643.600000 min\n", + "3 3.0 1590.500000 min\n", + "4 4.0 1786.200000 min\n", + "5 5.0 1664.900000 min\n", + "6 6.0 2037.800000 min\n", + "7 7.0 1825.100000 min\n", + "8 8.0 1823.300000 min\n", + "0 0.0 4182.000000 max\n", + "1 1.0 4187.600000 max\n", + "2 2.0 4785.000000 max\n", + "3 3.0 4189.500000 max\n", + "4 4.0 4384.400000 max\n", + "5 5.0 3976.400000 max\n", + "6 6.0 3699.600000 max\n", + "7 7.0 3361.800000 max\n", + "8 8.0 2994.900000 max" + ] + }, + "execution_count": 100, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# what about min/max?\n", + "\n", + "# aggregrate each count by risk level and type so we can graph together in altair\n", + "points_mean['type'] = 'mean'\n", + "\n", + "points_min = last_round.groupby('risk_level', as_index=False).aggregate('points').min()\n", + "points_min['type'] = 'min'\n", + "\n", + "points_max = last_round.groupby('risk_level', as_index=False).aggregate('points').max()\n", + "points_max['type'] = 'max'\n", + "\n", + "points_combined = pd.concat([points_mean, points_min, points_max])\n", + "\n", + "points_combined" + ] + }, + { + "cell_type": "code", + "execution_count": 102, + "id": "bd613aee-e588-4ad8-b812-7941fb44ce4e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 102, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "alt.Chart(points_combined).mark_line().encode(\n", + " x=alt.Y('risk_level:N', title='risk level'),\n", + " y=alt.Y('points', title='average points'),\n", + " color='type'\n", + ").properties(\n", + " title='points by risk level',\n", + " width=500,\n", + " height=400\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 104, + "id": "51f489e8-33d5-4bf3-af38-1edd1b5ca7c2", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 104, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "alt.Chart(last_round).mark_boxplot(extent=\"min-max\").encode(\n", + " alt.Y(\"points:Q\").scale(zero=False),\n", + " alt.X(\"risk_level:N\", title=\"risk level\"),\n", + ").properties(\n", + " title='range of points per agent by risk level',\n", + " width=500,\n", + " height=400\n", + ")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 109, + "id": "b8828b02-e533-47a5-bebc-d615dd027d9c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
risk_levelmaxmeanmin
004182.02818.0933961212.0
114187.62785.3552381417.5
224785.02705.0119831643.6
334189.52647.8186361590.5
444384.42583.9767441786.2
553976.42486.3307691664.9
663699.62454.1497802037.8
773361.82464.0656391825.1
882994.92444.1247791823.3
\n", + "
" + ], + "text/plain": [ + " risk_level max mean min\n", + "0 0 4182.0 2818.093396 1212.0\n", + "1 1 4187.6 2785.355238 1417.5\n", + "2 2 4785.0 2705.011983 1643.6\n", + "3 3 4189.5 2647.818636 1590.5\n", + "4 4 4384.4 2583.976744 1786.2\n", + "5 5 3976.4 2486.330769 1664.9\n", + "6 6 3699.6 2454.149780 2037.8\n", + "7 7 3361.8 2464.065639 1825.1\n", + "8 8 2994.9 2444.124779 1823.3" + ] + }, + "execution_count": 109, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# display the same information as a table\n", + "# for each run (= risk level), what are upper and lower values for individual points?\n", + "\n", + "points = []\n", + "\n", + "for i in range(9) :\n", + " run_i = last_round[last_round.risk_level == i]\n", + " # add one entry for each value with a type, so we can graph all at once in altair with a legend\n", + " points.append({\n", + " 'risk_level': i, \n", + " 'max': run_i.points.max(), \n", + " 'mean': run_i.points.mean(), \n", + " 'min': run_i.points.min()\n", + " })\n", + "\n", + "points_df = pd.DataFrame(points)\n", + "points_df" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 5ad18e0b91429b95dc2d163de08a3c4541a02557 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Fri, 6 Oct 2023 13:00:07 -0400 Subject: [PATCH 094/141] Don't try to calculate q1 when there are no risk levels --- simulatingrisk/risky_bet/model.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/simulatingrisk/risky_bet/model.py b/simulatingrisk/risky_bet/model.py index 54092a8..73adcf0 100644 --- a/simulatingrisk/risky_bet/model.py +++ b/simulatingrisk/risky_bet/model.py @@ -221,9 +221,12 @@ def risk_max(self): @property def risk_q1(self): - risk_median = self.risk_median - # first quartile is the median of values less than the median - return statistics.median([r for r in self.agent_risk_levels if r < risk_median]) + if self.agent_risk_levels: + risk_median = self.risk_median + # first quartile is the median of values less than the median + return statistics.median( + [r for r in self.agent_risk_levels if r < risk_median] + ) @property def risk_q3(self): From 152bcbd1ce0ce46f0cf73672c9bd143d4bedc8d4 Mon Sep 17 00:00:00 2001 From: Rebecca Sutton Koeser Date: Tue, 10 Oct 2023 14:16:01 -0400 Subject: [PATCH 095/141] Add diagrams to explain hawk/dove risk attitudes (#29) * Use math formatting for equations in REU explanation * Add table with risk attitudes (diagram 1) * Add table diagram * Revise chart to make it clear that risk level 8 never plays hawk * Tweak formatting for hawk/dove risk diagrams --- simulatingrisk/hawkdove/README.md | 64 ++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/simulatingrisk/hawkdove/README.md b/simulatingrisk/hawkdove/README.md index 51829fe..c1f3a7c 100644 --- a/simulatingrisk/hawkdove/README.md +++ b/simulatingrisk/hawkdove/README.md @@ -17,7 +17,7 @@ We want to know: what happens when different people have _different_ risk-attitu GAME: Hawk-Dove with risk-attitudes -Players arranged on a lattice [try both 4 neighbors (AYBD) and 8 neighbors (XYZABCDE)] +Players arranged on a lattice [options for both 4 neighbors (AYBD) and 8 neighbors (XYZABCDE)] | | | | |-|-|-| @@ -33,16 +33,62 @@ Players arranged on a lattice [try both 4 neighbors (AYBD) and 8 neighbors (XYZA - If I play HAWK and neighbor plays HAWK: 0 Each player on a lattice (grid in Mesa): -- Has parameter `r` [from 0 to 8, or 0 to 4 for four neighbors] -- Let `d` be the number of neighbors who played DOVE during the previous round. If `d > r`, then play HAWK. Otherwise play DOVE. (Agents who are risk-avoidant only play HAWK if there are a lot of doves around them. More risk-avoidance requires a higher number of doves to get an agent to play HAWK.) -- The proportion of neighbors who play DOVE corresponds to your probability of encountering a DOVE when playing a randomly-selected neighbor. The intended interpretation is that you maximize REU for this probability of your opponent playing DOVE. Thus, r corresponds to the probability above which playing HAWK maximizes REU. -- An REU maximizer will play HAWK when r(p) > [(D,H)-(H,H)]/[(H,D)-(D,D)] ; in other words, when r(p) > 0.52. An EU maximizer, with r(p) = p, will play HAWK when p > 0.52, e.g., when more than 4 out of 8 neighbors play DOVE. Thus, r = 4 corresponds to risk-neutrality (EU maximization), r < 4 corresponds to risk-inclination, and r > 4 corresponds to risk-avoidance. -- Payoffs were chosen to avoid the case in which two choices had equal expected utility for some number of neighbors. For example, if the payoff of (D,D) was (2,2), then at p = 0.5 (4 of 8 neighbors), then EU maximizers would be indifferent between HAWK and DOVE; in this case, no r-value would correspond to EU maximization, since r = 4 strictly prefers DOVE and r = 3 strictly prefers HAWK. - +- Has parameter $r$ [from 0 to 8, or 0 to 4 for four neighbors] +- Let `d` be the number of neighbors who played DOVE during the previous round. If $d > r$, then play HAWK. Otherwise play DOVE. (Agents who are risk-avoidant only play HAWK if there are a lot of doves around them. More risk-avoidance requires a higher number of doves to get an agent to play HAWK.) +- The proportion of neighbors who play DOVE corresponds to your probability of encountering a DOVE when playing a randomly-selected neighbor. The intended interpretation is that you maximize REU for this probability of your opponent playing DOVE. Thus, $r$ corresponds to the probability above which playing HAWK maximizes REU. - Choice for the first round could be randomly determined, or add parameters to see how initial conditions matter? - [OR VARY FIRST ROUND: what proportion starts as HAWK - - [Who is a HAWK and who is a DOVE is randomly determined; proportion set at the beginning of each simulation. E.g. 30% are HAWKS; if we have 100 players, then each player has a 30% chance of being HAWK] - - Call this initial parameter HAWK-ODDS + - Who is a HAWK and who is a DOVE is randomly determined; proportion set at the beginning of each simulation. E.g. 30% are HAWKS; if we have 100 players, then each player has a 30% chance of being HAWK; + - Call this initial parameter HAWK-ODDS; default is 50/50 +## Payoffs and risk attitudes + +This game has a discrete set of options instead of probability, so instead of defining `r` as a value between 0.0 and 1.0, we use discrete values based on the choices. For the game that includes diagonal neighbors when agents play all neighbors: + + + + + + + + + + + + + + + + + + + + +
r012345678
Plays H when:$\geq1$ D$\geq2$ D$\geq3$ D$\geq4$ D$\geq5$ D$\geq6$ D$\geq7$ D$\geq8$ Dnever
risk seekingEU maximizer
(risk neutral)
risk avoidant
+ + +An REU maximizer will play HAWK when +```math +r(p) > \frac{(D,H)-(H,H)}{(H,D)-(D,D)} +``` +In other words, when $r(p) > 0.52$. An EU maximizer, with $r(p) = p$, will play HAWK when $p > 0.52$, e.g., when more than 4 out of 8 neighbors play DOVE. Thus, $r = 4$ corresponds to risk-neutrality (EU maximization), $r < 4$ corresponds to risk-inclination, and $r > 4$ corresponds to risk-avoidance. + +Payoffs were chosen to avoid the case in which two choices had equal expected utility for some number of neighbors. For example, if the payoff of $(D,D)$ was $(2,2)$, then at $p = 0.5$ (4 of 8 neighbors), then EU maximizers would be indifferent between HAWK and DOVE; in this case, no r-value would correspond to EU maximization, since $r = 4$ strictly prefers DOVE and $r = 3$ strictly prefers HAWK. + +Another way to visualize the risk attitudes and choices in this game is this table, which shows when agents will play Hawk or Dove based on their risk attitudes (going down on the left side) and the number of neighbors playing Dove (across the top). + + + + + + + + + + + + + +
# of neighors playing DOVE
r012345678
risk seeking0DHHHHHHHH
1DDHHHHHHH
2DDDHHHHHH
3DDDDHHHHH
neutral4DDDDDHHHH
risk avoidant5DDDDDDHHH
6DDDDDDDHH
7DDDDDDDDH
8DDDDDDDDD
From 7a7fe5b7aedbb76a0f35ca338474dda3a2343ea3 Mon Sep 17 00:00:00 2001 From: Rebecca Sutton Koeser Date: Tue, 10 Oct 2023 14:17:57 -0400 Subject: [PATCH 096/141] Calculate & collect rolling percent hawk; detect convergence & stop #21 (#31) * Calculate & collect rolling percent hawk; detect convergence & stop #21 * Try to avoid empty median errors in q1/q3 tests --- simulatingrisk/hawkdove/app.py | 42 +++++++++++++++++------------ simulatingrisk/hawkdove/model.py | 44 ++++++++++++++++++++++++++++++- simulatingrisk/risky_bet/model.py | 15 ++++++----- 3 files changed, 76 insertions(+), 25 deletions(-) diff --git a/simulatingrisk/hawkdove/app.py b/simulatingrisk/hawkdove/app.py index af0bfea..24e25ac 100644 --- a/simulatingrisk/hawkdove/app.py +++ b/simulatingrisk/hawkdove/app.py @@ -31,16 +31,20 @@ def plot_hawks(model): model_df = model.datacollector.get_model_vars_dataframe().reset_index() - # calculate a rolling average for % hawk - model_df["rollingavg_percent_hawk"] = model_df.percent_hawk.rolling(10).mean() - # limit to last N rounds (how many ?) last_n_rounds = model_df.tail(50) + # determine domain of the chart; + # starting domain 0-50 so it doesn't jump / expand as much + max_index = max(model_df.last_valid_index() or 0, 50) + min_index = max(max_index - 50, 0) + bar_chart = ( alt.Chart(last_n_rounds) .mark_bar(color="orange") .encode( - x=alt.X("index", title="Step"), + x=alt.X( + "index", title="Step", scale=alt.Scale(domain=[min_index, max_index]) + ), y=alt.Y( "percent_hawk", title="Percent who chose hawk", @@ -48,21 +52,25 @@ def plot_hawks(model): ), ) ) - # graph rolling average as a line over the bar chart - line = ( - alt.Chart(last_n_rounds) - .mark_line(color="blue") - .encode( - x=alt.X("index", title="Step"), - y=alt.Y( - "rollingavg_percent_hawk", - title="% hawk (rolling average)", - scale=alt.Scale(domain=[0, 1]), - ), + # graph rolling average as a line over the bar chart, + # once we have enough rounds + if model_df.rolling_percent_hawk.any(): + line = ( + alt.Chart(last_n_rounds) + .mark_line(color="blue") + .encode( + x=alt.X("index", title="Step"), + y=alt.Y( + "rolling_percent_hawk", + title="% hawk (rolling average)", + scale=alt.Scale(domain=[0, 1]), + ), + ) ) - ) + # add the rolling average line on top of the bar chart + bar_chart += line - return solara.FigureAltair(bar_chart + line) + return solara.FigureAltair(bar_chart) page = JupyterViz( diff --git a/simulatingrisk/hawkdove/model.py b/simulatingrisk/hawkdove/model.py index 8db662f..986c479 100644 --- a/simulatingrisk/hawkdove/model.py +++ b/simulatingrisk/hawkdove/model.py @@ -1,5 +1,7 @@ from enum import Enum +from collections import deque import math +import statistics import mesa @@ -134,7 +136,12 @@ def points_rank(self): class HawkDoveModel(mesa.Model): """ """ + #: whether the simulation is running running = True # required for batch run + #: size of deque/fifo for recent values + rolling_window = 30 + #: minimum size before calculating rolling average + min_window = 15 def __init__( self, @@ -154,6 +161,10 @@ def __init__( # distribution of first choice (50/50 by default) self.hawk_odds = hawk_odds + # create fifos to track recent behavior to detect convergence + self.recent_percent_hawk = deque([], maxlen=self.rolling_window) + self.recent_rolling_percent_hawk = deque([], maxlen=self.rolling_window) + # initialize a single grid (each square inhabited by a single agent); # configure the grid to wrap around so everyone has neighbors self.grid = mesa.space.SingleGrid(grid_size, grid_size, True) @@ -175,6 +186,7 @@ def __init__( model_reporters={ "max_agent_points": "max_agent_points", "percent_hawk": "percent_hawk", + "rolling_percent_hawk": "rolling_percent_hawk", }, agent_reporters={ "risk_level": "risk_level", @@ -189,6 +201,12 @@ def step(self): """ self.schedule.step() self.datacollector.collect(self) + if self.converged: + self.running = False + print( + f"Stopping after {self.schedule.steps} rounds. " + + f"Final rolling average % hawk: {self.rolling_percent_hawk}" + ) @property def max_agent_points(self): @@ -199,4 +217,28 @@ def max_agent_points(self): def percent_hawk(self): # what percent of agents chose hawk? hawks = [a for a in self.schedule.agents if a.choice == Play.HAWK] - return len(hawks) / self.num_agents + phawk = len(hawks) / self.num_agents + # add to recent values + self.recent_percent_hawk.append(phawk) + return phawk + + @property + def rolling_percent_hawk(self): + # make sure we have enough values to check + if len(self.recent_percent_hawk) > self.min_window: + rolling_phawk = statistics.mean(self.recent_percent_hawk) + # add to recent values + self.recent_rolling_percent_hawk.append(rolling_phawk) + return rolling_phawk + + @property + def converged(self): + # check if the simulation is stable and should stop running + # calculating based on rolling percent hawk; when this is stable + # within our rolling window, return true + # - currently checking for single value; + # could allow for a small amount variation if necessary + return ( + len(self.recent_rolling_percent_hawk) > self.min_window + and len(set(self.recent_rolling_percent_hawk)) == 1 + ) diff --git a/simulatingrisk/risky_bet/model.py b/simulatingrisk/risky_bet/model.py index 73adcf0..70a0d85 100644 --- a/simulatingrisk/risky_bet/model.py +++ b/simulatingrisk/risky_bet/model.py @@ -221,15 +221,16 @@ def risk_max(self): @property def risk_q1(self): - if self.agent_risk_levels: - risk_median = self.risk_median - # first quartile is the median of values less than the median - return statistics.median( - [r for r in self.agent_risk_levels if r < risk_median] - ) + risk_median = self.risk_median + # first quartile is the median of values less than the median + submedian_values = [r for r in self.agent_risk_levels if r < risk_median] + if submedian_values: + return statistics.median(submedian_values) @property def risk_q3(self): risk_median = self.risk_median # third quartile is the median of values greater than the median - return statistics.median([r for r in self.agent_risk_levels if r > risk_median]) + supermedian_values = [r for r in self.agent_risk_levels if r > risk_median] + if supermedian_values: + return statistics.median(supermedian_values) From b3d36aec8e1def1d4b5b14c278d4858b9edfffe9 Mon Sep 17 00:00:00 2001 From: Rebecca Sutton Koeser Date: Fri, 13 Oct 2023 16:30:06 -0400 Subject: [PATCH 097/141] minimal solara app with access to all three current mesa models (#32) * Minimal solara app with access to all three current mesa models * Add placeholder about text; fix mistakenly repeated simulation * Update label --- simulatingrisk/about_app.md | 2 ++ simulatingrisk/app.py | 34 ++++++++++++++++++++++++++++++ simulatingrisk/hawkdove/server.py | 4 +--- simulatingrisk/risky_bet/server.py | 6 +++--- 4 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 simulatingrisk/about_app.md create mode 100644 simulatingrisk/app.py diff --git a/simulatingrisk/about_app.md b/simulatingrisk/about_app.md new file mode 100644 index 0000000..b2e97b8 --- /dev/null +++ b/simulatingrisk/about_app.md @@ -0,0 +1,2 @@ + +These simulatiions are associated with the CDH project [Simulating risk, risking simulations](https://cdh.princeton.edu/projects/simulating-risk/). diff --git a/simulatingrisk/app.py b/simulatingrisk/app.py new file mode 100644 index 0000000..10dfce4 --- /dev/null +++ b/simulatingrisk/app.py @@ -0,0 +1,34 @@ +import solara + +from simulatingrisk.hawkdove.app import page as hawkdove_page +from simulatingrisk.risky_bet.app import page as riskybet_page +from simulatingrisk.risky_food.app import page as riskyfood_page + + +@solara.component +def Home(): + with open("simulatingrisk/about_app.md") as readmefile: + return solara.Markdown("\n".join(readmefile.readlines())) + + +@solara.component +def hawkdove(): + return hawkdove_page + + +@solara.component +def riskybet(): + return riskybet_page + + +@solara.component +def riskyfood(): + return riskyfood_page + + +routes = [ + solara.Route(path="/", component=Home, label="Simulating Risk"), + solara.Route(path="hawkdove", component=hawkdove, label="Hawk/Dove"), + solara.Route(path="riskybet", component=riskybet, label="Risky Bet"), + solara.Route(path="riskyfood", component=riskyfood, label="Risky Food"), +] diff --git a/simulatingrisk/hawkdove/server.py b/simulatingrisk/hawkdove/server.py index d8a5e10..d996ef8 100644 --- a/simulatingrisk/hawkdove/server.py +++ b/simulatingrisk/hawkdove/server.py @@ -6,12 +6,10 @@ import solara import pandas as pd -from simulatingrisk.hawkdove.model import Play +from simulatingrisk.hawkdove.model import Play, divergent_colors_9, divergent_colors_5 def agent_portrayal(agent): - from simulatingrisk.hawkdove.model import divergent_colors_9, divergent_colors_5 - # initial display portrayal = { # styles for mesa runserver diff --git a/simulatingrisk/risky_bet/server.py b/simulatingrisk/risky_bet/server.py index 943ec90..8e8bf59 100644 --- a/simulatingrisk/risky_bet/server.py +++ b/simulatingrisk/risky_bet/server.py @@ -1,4 +1,7 @@ +import math + import mesa +from simulatingrisk.risky_bet.model import divergent_colors def risk_index(risk_level): @@ -21,9 +24,6 @@ def risk_index(risk_level): def agent_portrayal(agent): - import math - from simulatingrisk.risky_bet.model import divergent_colors - # initial display portrayal = { # styles for mesa runserver From 095554bb92179cee3fe84e261e8c0db2978c0715 Mon Sep 17 00:00:00 2001 From: Rebecca Sutton Koeser Date: Thu, 19 Oct 2023 10:39:07 -0400 Subject: [PATCH 098/141] Split hawk/dove simulation into two variants for single and variable risk attitudes (#35) * Split out hawk/dove into single and variable risk attitude versions * Update tests for hawk/dove refactor * Update app and batch run with split out single/variable hawk/dove sims --- simulatingrisk/app.py | 13 ++++- simulatingrisk/batch_run.py | 70 ++++++++++++++------------ simulatingrisk/hawkdove/app.py | 6 +-- simulatingrisk/hawkdove/model.py | 78 ++++++++++++++++++++--------- simulatingrisk/hawkdove/server.py | 12 ----- simulatingrisk/hawkdovevar/app.py | 25 +++++++++ simulatingrisk/hawkdovevar/model.py | 30 +++++++++++ tests/test_hawkdove.py | 60 +++++++++++++++------- 8 files changed, 203 insertions(+), 91 deletions(-) create mode 100644 simulatingrisk/hawkdovevar/app.py create mode 100644 simulatingrisk/hawkdovevar/model.py diff --git a/simulatingrisk/app.py b/simulatingrisk/app.py index 10dfce4..4911847 100644 --- a/simulatingrisk/app.py +++ b/simulatingrisk/app.py @@ -1,6 +1,7 @@ import solara from simulatingrisk.hawkdove.app import page as hawkdove_page +from simulatingrisk.hawkdovevar.app import page as hawkdove_var_page from simulatingrisk.risky_bet.app import page as riskybet_page from simulatingrisk.risky_food.app import page as riskyfood_page @@ -16,6 +17,11 @@ def hawkdove(): return hawkdove_page +@solara.component +def hawkdove_var(): + return hawkdove_var_page + + @solara.component def riskybet(): return riskybet_page @@ -28,7 +34,12 @@ def riskyfood(): routes = [ solara.Route(path="/", component=Home, label="Simulating Risk"), - solara.Route(path="hawkdove", component=hawkdove, label="Hawk/Dove"), + solara.Route( + path="hawkdove-single", component=hawkdove, label="Hawk/Dove (single r)" + ), + solara.Route( + path="hawkdove-variable", component=hawkdove_var, label="Hawk/Dove (variable r)" + ), solara.Route(path="riskybet", component=riskybet, label="Risky Bet"), solara.Route(path="riskyfood", component=riskyfood, label="Risky Food"), ] diff --git a/simulatingrisk/batch_run.py b/simulatingrisk/batch_run.py index dbfb5de..98c7be7 100755 --- a/simulatingrisk/batch_run.py +++ b/simulatingrisk/batch_run.py @@ -6,7 +6,8 @@ from mesa import batch_run -from simulatingrisk.hawkdove.model import HawkDoveModel +from simulatingrisk.hawkdove.model import HawkDoveSingleRiskModel +from simulatingrisk.hawkdovevar.model import HawkDoveVariableRiskModel from simulatingrisk.risky_bet.model import RiskyBetModel from simulatingrisk.risky_food.model import RiskyFoodModel @@ -47,38 +48,24 @@ def riskyfood_batch_run(args=None): save_results("riskyfood", results) -def hawkdove_batch_run(args): +def hawkdove_singlerisk_batch_run(args): # params are: # grid_size, # include_diagonals=True, - # risk_attitudes="variable", or single # agent_risk_level=None, # hawk_odds=0.5, - if args.risk_attitudes == "variable": - params = { - "grid_size": 20, - "risk_attitudes": "variable", - } - iterations = 5 - elif args.risk_attitudes == "single": - params = { - "grid_size": 20, - "risk_attitudes": "single", - "agent_risk_level": [0, 1, 2, 3, 4, 5, 6, 7, 8], - } - iterations = 1 + params = { + "grid_size": 20, + "agent_risk_level": [0, 1, 2, 3, 4, 5, 6, 7, 8], + } + iterations = 1 results = batch_run( - HawkDoveModel, + HawkDoveSingleRiskModel, # when including diagonals, risk levels go from 0 to 8; # probably do not need to include the extremes for this analysis parameters=params, - # "grid_size": 20, - # "risk_attitudes": "variable", - # "risk_attitudes": "single", - # "agent_risk_level": [0, 1, 2, 3, 4, 5, 6, 7, 8], - # }, iterations=iterations, number_processes=1, data_collection_period=1, @@ -86,7 +73,25 @@ def hawkdove_batch_run(args): max_steps=200, # converges very quickly, so don't run 1000 times ) # include the mode in the output filename - save_results("hawkdove_risk-%s" % args.risk_attitudes, results) + save_results("hawkdove_single", results) + + +def hawkdove_variablerisk_batch_run(args): + params = { + "grid_size": 20, + } + iterations = 5 + results = batch_run( + HawkDoveVariableRiskModel, + parameters=params, + iterations=iterations, + number_processes=1, + data_collection_period=1, + display_progress=True, + max_steps=200, # converges very quickly, so don't run 1000 times + ) + # include the mode in the output filename + save_results("hawkdove_variable", results) def save_results(simulation, results): @@ -117,14 +122,17 @@ def save_results(simulation, results): riskybet_parser.set_defaults(func=riskybet_batch_run) riskyfood_parser = subparsers.add_parser("riskyfood") riskyfood_parser.set_defaults(func=riskyfood_batch_run) - hawkdove_parser = subparsers.add_parser("hawkdove") - hawkdove_parser.add_argument( - "-r", - "--risk-attitudes", - choices=["single", "variable"], - help="Mode for initializing agent risk attitudes", - ) - hawkdove_parser.set_defaults(func=hawkdove_batch_run) + hawkdove_parser = subparsers.add_parser("hawkdove-single") + # will any subparser arguments be needed in future? + # hawkdove_parser.add_argument( + # "-r", + # "--risk-attitudes", + # choices=["single", "variable"], + # help="Mode for initializing agent risk attitudes", + # ) + hawkdove_parser.set_defaults(func=hawkdove_singlerisk_batch_run) + hawkdovevar_parser = subparsers.add_parser("hawkdove-var") + hawkdovevar_parser.set_defaults(func=hawkdove_variablerisk_batch_run) args = parser.parse_args() # run appropriate function based on the selected subcommand diff --git a/simulatingrisk/hawkdove/app.py b/simulatingrisk/hawkdove/app.py index 24e25ac..39c74d6 100644 --- a/simulatingrisk/hawkdove/app.py +++ b/simulatingrisk/hawkdove/app.py @@ -5,7 +5,7 @@ import solara -from simulatingrisk.hawkdove.model import HawkDoveModel +from simulatingrisk.hawkdove.model import HawkDoveSingleRiskModel from simulatingrisk.hawkdove.server import ( agent_portrayal, jupyterviz_params, @@ -74,10 +74,10 @@ def plot_hawks(model): page = JupyterViz( - HawkDoveModel, + HawkDoveSingleRiskModel, jupyterviz_params, measures=[plot_hawks], - name="Hawk/Dove with risk attitudes", + name="Hawk/Dove game with risk attitudes; all agents have the same risk attitude", agent_portrayal=agent_portrayal, space_drawer=draw_hawkdove_agent_space, ) diff --git a/simulatingrisk/hawkdove/model.py b/simulatingrisk/hawkdove/model.py index 986c479..0a08e9e 100644 --- a/simulatingrisk/hawkdove/model.py +++ b/simulatingrisk/hawkdove/model.py @@ -35,32 +35,27 @@ class HawkDoveAgent(mesa.Agent): An agent with a risk attitude playing Hawk or Dove """ - def __init__(self, unique_id, model, risk_level=None, hawk_odds=None): + def __init__(self, unique_id, model, hawk_odds=None): super().__init__(unique_id, model) self.points = 0 self.choice = self.initial_choice(hawk_odds) self.last_choice = None - # risk level - # - based partially on neighborhood size, - # which is configurable at the model level - num_neighbors = 8 if self.model.include_diagonals else 4 - # if risk level is None, generate a random risk level - # NOTE: this means we allow risk level zero - if risk_level is None: - self.risk_level = self.random.randint(0, num_neighbors) - else: - # otherwise, used as passed - self.risk_level = risk_level + # risk level must be set by base class, since initial + # conditions are specific to single / variable risk games + self.set_risk_level() + + def set_risk_level(self): + raise NotImplementedError def __repr__(self): - return f"" + return f"<{self.__class__.__name__} id={self.unique_id} r={self.risk_level}>" def initial_choice(self, hawk_odds=None): # first round : choose what to play randomly or based on initial hawk odds opts = {} - if hawk_odds: + if hawk_odds is not None: opts["weight"] = hawk_odds return coinflip(play_choices, **opts) @@ -142,13 +137,13 @@ class HawkDoveModel(mesa.Model): rolling_window = 30 #: minimum size before calculating rolling average min_window = 15 + #: class to use when initializing agents + agent_class = HawkDoveAgent def __init__( self, grid_size, include_diagonals=True, - risk_attitudes="variable", - agent_risk_level=None, hawk_odds=0.5, ): super().__init__() @@ -157,7 +152,6 @@ def __init__( # mesa get_neighbors supports moore neighborhood (include diagonals) # and von neumann (exclude diagonals) self.include_diagonals = include_diagonals - self.risk_attitudes = risk_attitudes # distribution of first choice (50/50 by default) self.hawk_odds = hawk_odds @@ -170,16 +164,12 @@ def __init__( self.grid = mesa.space.SingleGrid(grid_size, grid_size, True) self.schedule = mesa.time.StagedActivation(self, ["choose", "play"]) - agent_opts = {} - # when started in single risk attitude mode, initialize all agents - # with the specified risk level - if risk_attitudes == "single" and agent_risk_level is not None: - agent_opts["risk_level"] = agent_risk_level - + # initialize all agents + agent_opts = self.new_agent_options() for i in range(self.num_agents): - agent = HawkDoveAgent(i, self, hawk_odds=self.hawk_odds, **agent_opts) + # add to scheduler and place randomly in an empty spot + agent = self.agent_class(i, self, **agent_opts) self.schedule.add(agent) - # place randomly in an empty spot self.grid.move_to_empty(agent) self.datacollector = mesa.DataCollector( @@ -195,6 +185,11 @@ def __init__( }, ) + def new_agent_options(self): + # generate and return a dictionary with common options + # for initializing all agents + return {"hawk_odds": self.hawk_odds} + def step(self): """ A model step. Used for collecting data and advancing the schedule @@ -242,3 +237,36 @@ def converged(self): len(self.recent_rolling_percent_hawk) > self.min_window and len(set(self.recent_rolling_percent_hawk)) == 1 ) + + +class HawkDoveSingleRiskAgent(HawkDoveAgent): + """ + An agent with a risk attitude playing Hawk or Dove; must be initialized + with a risk level + """ + + def set_risk_level(self): + self.risk_level = self.model.agent_risk_level + + +class HawkDoveSingleRiskModel(HawkDoveModel): + """hawk/dove simulation where all agents have the same risk atttitude""" + + #: class to use when initializing agents + agent_class = HawkDoveSingleRiskAgent + + risk_attitudes = "single" + + def __init__( + self, + grid_size, + agent_risk_level, + include_diagonals=True, + hawk_odds=0.5, + ): + # store agent risk level + self.agent_risk_level = agent_risk_level + # pass through options and initialize base class + super().__init__( + grid_size, include_diagonals=include_diagonals, hawk_odds=hawk_odds + ) diff --git a/simulatingrisk/hawkdove/server.py b/simulatingrisk/hawkdove/server.py index d996ef8..a4039f0 100644 --- a/simulatingrisk/hawkdove/server.py +++ b/simulatingrisk/hawkdove/server.py @@ -72,12 +72,6 @@ def agent_portrayal(agent): "value": True, "label": "Include diagonal neighbors", }, - "risk_attitudes": { - "type": "Select", - "value": "variable", - "values": ["variable", "single"], - "description": "Agent initial risk level", - }, "agent_risk_level": { "type": "SliderInt", "min": 0, @@ -93,12 +87,6 @@ def agent_portrayal(agent): "max": 1.0, "step": 0.1, }, - # "risk_adjustment": { - # "type": "Select", - # "value": "adopt", - # "values": ["adopt", "average"], - # "description": "How agents update their risk level", - # }, } diff --git a/simulatingrisk/hawkdovevar/app.py b/simulatingrisk/hawkdovevar/app.py new file mode 100644 index 0000000..79bf84d --- /dev/null +++ b/simulatingrisk/hawkdovevar/app.py @@ -0,0 +1,25 @@ +# solara/jupyterviz app +from mesa.experimental import JupyterViz + + +from simulatingrisk.hawkdovevar.model import HawkDoveVariableRiskModel +from simulatingrisk.hawkdove.server import ( + agent_portrayal, + jupyterviz_params, + draw_hawkdove_agent_space, +) +from simulatingrisk.hawkdove.app import plot_hawks + +jupyterviz_params_var = jupyterviz_params.copy() +del jupyterviz_params_var["agent_risk_level"] + +page = JupyterViz( + HawkDoveVariableRiskModel, + jupyterviz_params_var, + measures=[plot_hawks], + name="Hawk/Dove game with variable risk attitudes", + agent_portrayal=agent_portrayal, + space_drawer=draw_hawkdove_agent_space, +) +# required to render the visualization with Jupyter/Solara +page diff --git a/simulatingrisk/hawkdovevar/model.py b/simulatingrisk/hawkdovevar/model.py new file mode 100644 index 0000000..b4b28fb --- /dev/null +++ b/simulatingrisk/hawkdovevar/model.py @@ -0,0 +1,30 @@ +from simulatingrisk.hawkdove.model import HawkDoveModel, HawkDoveAgent + + +class HawkDoveVariableRiskAgent(HawkDoveAgent): + """ + An agent with random risk attitude playing Hawk or Dove + """ + + def set_risk_level(self): + # risk level is based partially on neighborhood size, + # which is configurable at the model level + num_neighbors = 8 if self.model.include_diagonals else 4 + # generate a random risk level + self.risk_level = self.random.randint(0, num_neighbors) + + +class HawkDoveVariableRiskModel(HawkDoveModel): + risk_attitudes = "variable" + agent_class = HawkDoveVariableRiskAgent + + def __init__( + self, + grid_size, + include_diagonals=True, + hawk_odds=0.5, + ): + super().__init__( + grid_size, include_diagonals=include_diagonals, hawk_odds=hawk_odds + ) + # no custom logic or params yet, but will be adding risk updating logic diff --git a/tests/test_hawkdove.py b/tests/test_hawkdove.py index 93ce8ef..48c7a70 100644 --- a/tests/test_hawkdove.py +++ b/tests/test_hawkdove.py @@ -2,23 +2,33 @@ from unittest.mock import Mock, patch from collections import Counter -from simulatingrisk.hawkdove.model import HawkDoveModel, HawkDoveAgent, Play +import pytest + +from simulatingrisk.hawkdove.model import ( + HawkDoveAgent, + Play, + HawkDoveSingleRiskModel, + HawkDoveSingleRiskAgent, +) +from simulatingrisk.hawkdovevar.model import HawkDoveVariableRiskModel def test_agent_neighbors(): # initialize model with a small grid, include diagonals - model = HawkDoveModel(3, include_diagonals=True) + model = HawkDoveSingleRiskModel(3, include_diagonals=True, agent_risk_level=4) # every agent should have 8 neighbors when diagonals are included assert all([len(agent.neighbors) == 8 for agent in model.schedule.agents]) # every agent should have 4 neighbors when diagonals are not included - model = HawkDoveModel(3, include_diagonals=False) + model = HawkDoveSingleRiskModel(3, include_diagonals=False, agent_risk_level=2) assert all([len(agent.neighbors) == 4 for agent in model.schedule.agents]) def test_agent_initial_choice(): grid_size = 100 - model = HawkDoveModel(grid_size, include_diagonals=False) + model = HawkDoveSingleRiskModel( + grid_size, include_diagonals=False, agent_risk_level=5 + ) # for now, initial choice is random (hawk-odds param still todo) initial_choices = [a.choice for a in model.schedule.agents] choice_count = Counter(initial_choices) @@ -31,7 +41,9 @@ def test_agent_initial_choice(): def test_agent_initial_choice_hawkodds(): grid_size = 100 # specify hawk-odds other than 05 - model = HawkDoveModel(grid_size, include_diagonals=False, hawk_odds=0.3) + model = HawkDoveSingleRiskModel( + grid_size, include_diagonals=False, hawk_odds=0.3, agent_risk_level=2 + ) initial_choices = [a.choice for a in model.schedule.agents] choice_count = Counter(initial_choices) # expect about 30% hawks @@ -39,37 +51,47 @@ def test_agent_initial_choice_hawkodds(): assert math.isclose(choice_count[Play.HAWK], expected_hawks, rel_tol=0.05) +def test_base_agent_risk_level(): + # base class should raise error because method to set risk level is not defined + with pytest.raises(NotImplementedError): + HawkDoveAgent(1, Mock()) + + def test_agent_initial_risk_level(): - agent = HawkDoveAgent(1, Mock(), risk_level=2) + # single risk agent sets risk level based on model + agent = HawkDoveSingleRiskAgent(1, Mock(agent_risk_level=2)) assert agent.risk_level == 2 def test_agent_repr(): agent_id = 1 risk_level = 3 - agent = HawkDoveAgent(agent_id, Mock(), risk_level=risk_level) - assert repr(agent) == f"" + agent = HawkDoveSingleRiskAgent(agent_id, Mock(agent_risk_level=risk_level)) + assert repr(agent) == f"" def test_model_single_risk_level(): risk_level = 3 - model = HawkDoveModel( - 5, include_diagonals=True, risk_attitudes="single", agent_risk_level=risk_level + model = HawkDoveSingleRiskModel( + 5, include_diagonals=True, agent_risk_level=risk_level ) for agent in model.schedule.agents: assert agent.risk_level == risk_level # handle zero properly (should not be treated the same as None) risk_level = 0 - model = HawkDoveModel( - 5, include_diagonals=True, risk_attitudes="single", agent_risk_level=risk_level + model = HawkDoveSingleRiskModel( + 5, include_diagonals=True, agent_risk_level=risk_level ) for agent in model.schedule.agents: assert agent.risk_level == risk_level -def test_model_variable_risk_level(): - model = HawkDoveModel(5, include_diagonals=True, risk_attitudes="variable") +def test_variable_risk_level(): + model = HawkDoveVariableRiskModel( + 5, + include_diagonals=True, + ) # when risk level is variable/random, agents should have different risk levels risk_levels = set([agent.risk_level for agent in model.schedule.agents]) assert len(risk_levels) > 1 @@ -77,7 +99,7 @@ def test_model_variable_risk_level(): def test_num_dove_neighbors(): # initialize an agent with a mock model - agent = HawkDoveAgent(1, Mock()) + agent = HawkDoveSingleRiskAgent(1, Mock(agent_risk_level=2)) mock_neighbors = [ Mock(last_choice=Play.HAWK), Mock(last_choice=Play.HAWK), @@ -85,12 +107,12 @@ def test_num_dove_neighbors(): Mock(last_choice=Play.DOVE), ] - with patch.object(HawkDoveAgent, "neighbors", mock_neighbors): + with patch.object(HawkDoveSingleRiskAgent, "neighbors", mock_neighbors): assert agent.num_dove_neighbors == 1 def test_agent_choose(): - agent = HawkDoveAgent(1, Mock()) + agent = HawkDoveSingleRiskAgent(1, Mock(agent_risk_level=3)) # on the first round, nothing should happen (uses initial choice) agent.model.schedule.steps = 0 agent.choose() @@ -135,8 +157,8 @@ def test_agent_payoff(): # If I play DOVE and neighbor plays HAWK: 1 # If I play HAWK and neighbor plays HAWK: 0 - agent = HawkDoveAgent(1, Mock()) - other_agent = HawkDoveAgent(2, Mock()) + agent = HawkDoveSingleRiskAgent(1, Mock(agent_risk_level=2)) + other_agent = HawkDoveSingleRiskAgent(2, Mock(agent_risk_level=3)) # If I play HAWK and neighbor plays DOVE: 3 agent.choice = Play.HAWK other_agent.choice = Play.DOVE From 105752cc4e63088b4c5fe3e350a39f55276ceb20 Mon Sep 17 00:00:00 2001 From: Rebecca Sutton Koeser Date: Wed, 25 Oct 2023 13:43:23 -0400 Subject: [PATCH 099/141] Hawk/dove updating risk attitudes (#36) * Preliminary work for hawk/dove adaptive risk attitudes #20 * Clean up duplicate code; add comments about adjusting parameters * Add tests for variable hawk/dove; improve error handling * Add a label for round-adjustment parameter * Fix typo in app about placeholder content * Load about app content relative to app file * Remove model dependency in matplotlib histogram (prevents from updating) * Remove unused requirements text files/directories * Clean up outdated comments and refactor common hawk/dove params --- requirements.txt | 1 - requirements/main.txt | 0 simulatingrisk/about_app.md | 2 +- simulatingrisk/app.py | 5 +- simulatingrisk/charts/histogram.py | 8 +- simulatingrisk/hawkdove/model.py | 18 +++- simulatingrisk/hawkdove/server.py | 21 ++-- simulatingrisk/hawkdovevar/app.py | 25 ++++- simulatingrisk/hawkdovevar/model.py | 84 +++++++++++++++- tests/test_hawkdove.py | 16 +-- tests/test_hawkdovevar.py | 150 ++++++++++++++++++++++++++++ 11 files changed, 293 insertions(+), 37 deletions(-) delete mode 100644 requirements.txt delete mode 100644 requirements/main.txt create mode 100644 tests/test_hawkdovevar.py diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index e07f847..0000000 --- a/requirements.txt +++ /dev/null @@ -1 +0,0 @@ --r requirements/main.txt \ No newline at end of file diff --git a/requirements/main.txt b/requirements/main.txt deleted file mode 100644 index e69de29..0000000 diff --git a/simulatingrisk/about_app.md b/simulatingrisk/about_app.md index b2e97b8..02ea7f2 100644 --- a/simulatingrisk/about_app.md +++ b/simulatingrisk/about_app.md @@ -1,2 +1,2 @@ -These simulatiions are associated with the CDH project [Simulating risk, risking simulations](https://cdh.princeton.edu/projects/simulating-risk/). +These simulations are associated with the CDH project [Simulating risk, risking simulations](https://cdh.princeton.edu/projects/simulating-risk/). diff --git a/simulatingrisk/app.py b/simulatingrisk/app.py index 4911847..9eda061 100644 --- a/simulatingrisk/app.py +++ b/simulatingrisk/app.py @@ -1,3 +1,5 @@ +import os.path + import solara from simulatingrisk.hawkdove.app import page as hawkdove_page @@ -8,7 +10,8 @@ @solara.component def Home(): - with open("simulatingrisk/about_app.md") as readmefile: + # load about markdown file in the same directory + with open(os.path.join(os.path.dirname(__file__), "about_app.md")) as readmefile: return solara.Markdown("\n".join(readmefile.readlines())) diff --git a/simulatingrisk/charts/histogram.py b/simulatingrisk/charts/histogram.py index 46c9d6a..b8cbe92 100644 --- a/simulatingrisk/charts/histogram.py +++ b/simulatingrisk/charts/histogram.py @@ -61,16 +61,12 @@ def plot_risk_histogram(model): # adapted from mesa visualiation tutorial # https://mesa.readthedocs.io/en/stable/tutorials/visualization_tutorial.html#Building-your-own-visualization-component - # Note: you must initialize a figure using this method instead of + # Note: per Mesa docs, has to be initialized using this method instead of # plt.figure(), for thread safety purpose fig = Figure() ax = fig.subplots() # generate a histogram of current risk levels risk_levels = [agent.risk_level for agent in model.schedule.agents] - # Note: you have to use Matplotlib's OOP API instead of plt.hist - # because plt.hist is not thread-safe. ax.hist(risk_levels, bins=risk_bins) ax.set_title("risk levels") - # You have to specify the dependencies as follows, so that the figure - # auto-updates when viz.model or viz.df is changed. - solara.FigureMatplotlib(fig, dependencies=[model]) + solara.FigureMatplotlib(fig) diff --git a/simulatingrisk/hawkdove/model.py b/simulatingrisk/hawkdove/model.py index 0a08e9e..fdb57f9 100644 --- a/simulatingrisk/hawkdove/model.py +++ b/simulatingrisk/hawkdove/model.py @@ -50,7 +50,10 @@ def set_risk_level(self): raise NotImplementedError def __repr__(self): - return f"<{self.__class__.__name__} id={self.unique_id} r={self.risk_level}>" + return ( + f"<{self.__class__.__name__} id={self.unique_id} " + + f"r={self.risk_level} points={self.points}>" + ) def initial_choice(self, hawk_odds=None): # first round : choose what to play randomly or based on initial hawk odds @@ -129,7 +132,18 @@ def points_rank(self): class HawkDoveModel(mesa.Model): - """ """ + """ + Model for hawk/dove game with risk attitudes. + + :param grid_size: number for square grid size (creates n*n agents) + :param include_diagonals: whether agents should include diagonals + or not when considering neighbors (default: True) + :param hawk_odds: odds for playing hawk on the first round (default: 0.5) + :param risk_adjustment: strategy agents should use for adjusting risk; + None (default), adopt, or average + :param adjust_every: when risk adjustment is enabled, adjust every + N rounds (default: 10) + """ #: whether the simulation is running running = True # required for batch run diff --git a/simulatingrisk/hawkdove/server.py b/simulatingrisk/hawkdove/server.py index a4039f0..0e89587 100644 --- a/simulatingrisk/hawkdove/server.py +++ b/simulatingrisk/hawkdove/server.py @@ -57,8 +57,8 @@ def agent_portrayal(agent): "grid_size": grid_size, } - -jupyterviz_params = { +# parameters common to both hawk/dove variants +common_jupyterviz_params = { "grid_size": { "type": "SliderInt", "value": grid_size, @@ -72,13 +72,6 @@ def agent_portrayal(agent): "value": True, "label": "Include diagonal neighbors", }, - "agent_risk_level": { - "type": "SliderInt", - "min": 0, - "max": 8, - "step": 1, - "value": 2, - }, "hawk_odds": { "type": "SliderFloat", "value": 0.5, @@ -89,6 +82,16 @@ def agent_portrayal(agent): }, } +# in single-risk variant, risk level is set for all agents at init time +jupyterviz_params = common_jupyterviz_params.copy() +jupyterviz_params["agent_risk_level"] = { + "type": "SliderInt", + "min": 0, + "max": 8, + "step": 1, + "value": 2, +} + def draw_hawkdove_agent_space(model, agent_portrayal): # custom agent space chart, modeled on default diff --git a/simulatingrisk/hawkdovevar/app.py b/simulatingrisk/hawkdovevar/app.py index 79bf84d..346084d 100644 --- a/simulatingrisk/hawkdovevar/app.py +++ b/simulatingrisk/hawkdovevar/app.py @@ -5,13 +5,32 @@ from simulatingrisk.hawkdovevar.model import HawkDoveVariableRiskModel from simulatingrisk.hawkdove.server import ( agent_portrayal, - jupyterviz_params, + common_jupyterviz_params, draw_hawkdove_agent_space, ) from simulatingrisk.hawkdove.app import plot_hawks -jupyterviz_params_var = jupyterviz_params.copy() -del jupyterviz_params_var["agent_risk_level"] +# start with common hawk/dove params, then add params for variable risk +jupyterviz_params_var = common_jupyterviz_params.copy() +jupyterviz_params_var.update( + { + "risk_adjustment": { + "type": "Select", + "value": "adopt", + "values": ["none", "adopt", "average"], + "description": "If and how agents update their risk level", + }, + "adjust_every": { + "label": "Adjustment frequency (# rounds)", + "type": "SliderInt", + "min": 1, + "max": 30, + "step": 1, + "value": 10, + "description": "How many rounds between risk adjustment", + }, + } +) page = JupyterViz( HawkDoveVariableRiskModel, diff --git a/simulatingrisk/hawkdovevar/model.py b/simulatingrisk/hawkdovevar/model.py index b4b28fb..31d481e 100644 --- a/simulatingrisk/hawkdovevar/model.py +++ b/simulatingrisk/hawkdovevar/model.py @@ -1,9 +1,13 @@ +import statistics + from simulatingrisk.hawkdove.model import HawkDoveModel, HawkDoveAgent class HawkDoveVariableRiskAgent(HawkDoveAgent): """ - An agent with random risk attitude playing Hawk or Dove + An agent with random risk attitude playing Hawk or Dove. Optionally + adjusts risks based on most successful neighbor, depending on model + configuration. """ def set_risk_level(self): @@ -13,18 +17,94 @@ def set_risk_level(self): # generate a random risk level self.risk_level = self.random.randint(0, num_neighbors) + def play(self): + super().play() + # when enabled by the model, periodically adjust risk level + if self.model.adjustment_round: + self.adjust_risk() + + @property + def most_successful_neighbor(self): + """identify and return the neighbor with the most points""" + # sort neighbors by points, highest points first + # adapted from risky bet wealthiest neighbor + return sorted(self.neighbors, key=lambda x: x.points, reverse=True)[0] + + def adjust_risk(self): + # look at neighbors + # if anyone has more points + # either adopt their risk attitude or average theirs with yours + + best = self.most_successful_neighbor + # if most successful neighbor has more points and a different + # risk attitude, adjust + if best.points > self.points and best.risk_level != self.risk_level: + # adjust risk based on model configuration + if self.model.risk_adjustment == "adopt": + # adopt neighbor's risk level + self.risk_level = best.risk_level + elif self.model.risk_adjustment == "average": + # average theirs with mine, then round to a whole number + # since this model uses discrete risk levels + self.risk_level = round( + statistics.mean([self.risk_level, best.risk_level]) + ) + class HawkDoveVariableRiskModel(HawkDoveModel): + """ + Model for hawk/dove game with variable risk attitudes. + + :param grid_size: number for square grid size (creates n*n agents) + :param include_diagonals: whether agents should include diagonals + or not when considering neighbors (default: True) + :param hawk_odds: odds for playing hawk on the first round (default: 0.5) + :param risk_adjustment: strategy agents should use for adjusting risk; + None (default), adopt, or average + :param adjust_every: when risk adjustment is enabled, adjust every + N rounds (default: 10) + """ + risk_attitudes = "variable" agent_class = HawkDoveVariableRiskAgent + supported_risk_adjustments = (None, "adopt", "average") + def __init__( self, grid_size, include_diagonals=True, hawk_odds=0.5, + risk_adjustment=None, + adjust_every=10, ): super().__init__( grid_size, include_diagonals=include_diagonals, hawk_odds=hawk_odds ) - # no custom logic or params yet, but will be adding risk updating logic + # convert string input from solara app parameters to None + if risk_adjustment == "none": + risk_adjustment = None + # make sure risk adjustment is valid + if risk_adjustment not in self.supported_risk_adjustments: + risk_adjust_opts = ", ".join( + [opt or "none" for opt in self.supported_risk_adjustments] + ) + raise ValueError( + f"Unsupported risk adjustment '{risk_adjustment}'; " + + f"must be one of {risk_adjust_opts}" + ) + + self.risk_adjustment = risk_adjustment + self.adjust_round_n = adjust_every + + @property + def adjustment_round(self) -> bool: + """is the current round an adjustment round?""" + # check if the current step is an adjustment round + # when risk adjustment is enabled, agents should adjust their risk + # strategy every N rounds; + return ( + self.risk_adjustment + and self.schedule.steps > 0 + and self.schedule.steps % self.adjust_round_n == 0 + ) diff --git a/tests/test_hawkdove.py b/tests/test_hawkdove.py index 48c7a70..d4ed76f 100644 --- a/tests/test_hawkdove.py +++ b/tests/test_hawkdove.py @@ -10,7 +10,6 @@ HawkDoveSingleRiskModel, HawkDoveSingleRiskAgent, ) -from simulatingrisk.hawkdovevar.model import HawkDoveVariableRiskModel def test_agent_neighbors(): @@ -67,7 +66,10 @@ def test_agent_repr(): agent_id = 1 risk_level = 3 agent = HawkDoveSingleRiskAgent(agent_id, Mock(agent_risk_level=risk_level)) - assert repr(agent) == f"" + assert ( + repr(agent) + == f"" + ) def test_model_single_risk_level(): @@ -87,16 +89,6 @@ def test_model_single_risk_level(): assert agent.risk_level == risk_level -def test_variable_risk_level(): - model = HawkDoveVariableRiskModel( - 5, - include_diagonals=True, - ) - # when risk level is variable/random, agents should have different risk levels - risk_levels = set([agent.risk_level for agent in model.schedule.agents]) - assert len(risk_levels) > 1 - - def test_num_dove_neighbors(): # initialize an agent with a mock model agent = HawkDoveSingleRiskAgent(1, Mock(agent_risk_level=2)) diff --git a/tests/test_hawkdovevar.py b/tests/test_hawkdovevar.py new file mode 100644 index 0000000..b6b98bb --- /dev/null +++ b/tests/test_hawkdovevar.py @@ -0,0 +1,150 @@ +import statistics +from unittest.mock import patch, Mock + +import pytest + +from simulatingrisk.hawkdovevar.model import ( + HawkDoveVariableRiskModel, + HawkDoveVariableRiskAgent, +) + + +def test_init(): + model = HawkDoveVariableRiskModel(5) + # defaults + assert model.risk_adjustment is None + assert model.hawk_odds == 0.5 + assert model.include_diagonals is True + # unused but should be set to default + assert model.adjust_round_n == 10 + + # init with risk adjustment + model = HawkDoveVariableRiskModel( + 5, + include_diagonals=False, + hawk_odds=0.2, + risk_adjustment="adopt", + adjust_every=5, + ) + + assert model.risk_adjustment == "adopt" + assert model.adjust_round_n == 5 + assert model.hawk_odds == 0.2 + assert model.include_diagonals is False + + # handle string none for solara app parameters + model = HawkDoveVariableRiskModel(5, risk_adjustment="none") + assert model.risk_adjustment is None + + # complain about invalid adjustment type + with pytest.raises(ValueError, match="Unsupported risk adjustment 'bogus'"): + HawkDoveVariableRiskModel(3, risk_adjustment="bogus") + + +def test_init_variable_risk_level(): + model = HawkDoveVariableRiskModel( + 5, + include_diagonals=True, + ) + # when risk level is variable/random, agents should have different risk levels + risk_levels = set([agent.risk_level for agent in model.schedule.agents]) + assert len(risk_levels) > 1 + + +adjustment_testdata = [ + # init parameters, expected adjustment round + ({"risk_adjustment": None}, None), + ({"risk_adjustment": "adopt"}, 10), + ({"risk_adjustment": "average"}, 10), + ({"risk_adjustment": "average", "adjust_every": 3}, 3), +] + + +@pytest.mark.parametrize("params,expect_adjust_step", adjustment_testdata) +def test_adjustment_round(params, expect_adjust_step): + model = HawkDoveVariableRiskModel(3, **params) + + run_for = (expect_adjust_step or 10) + 1 + + # step through the model enough rounds to encounter one adjustment rounds + # if adjustment is enabled; start at 1 (step count starts at 1) + for i in range(1, run_for): + model.step() + if i == expect_adjust_step: + assert model.adjustment_round + else: + assert not model.adjustment_round + + +def test_most_successful_neighbor(): + # initialize an agent with a mock model + agent = HawkDoveVariableRiskAgent(1, Mock(), 1000) + mock_neighbors = [ + Mock(points=2), + Mock(points=4), + Mock(points=23), + Mock(points=31), + ] + + with patch.object(HawkDoveVariableRiskAgent, "neighbors", mock_neighbors): + assert agent.most_successful_neighbor.points == 31 + + +def test_agent_play_adjust(): + mock_model = Mock(risk_adjustment="adopt") + agent = HawkDoveVariableRiskAgent(1, mock_model) + # simulate no neighbors to skip payoff calculation + with patch.object( + HawkDoveVariableRiskAgent, "neighbors", new=[] + ) as mock_adjust_risk: + with patch.object(HawkDoveVariableRiskAgent, "adjust_risk") as mock_adjust_risk: + # when it is not an adjustment round, should not call adjust risk + mock_model.adjustment_round = False + agent.play() + assert mock_adjust_risk.call_count == 0 + + # should call adjust risk when the model indicates + mock_model.adjustment_round = True + agent.play() + assert mock_adjust_risk.call_count == 1 + + +def test_adjust_risk_adopt(): + # initialize an agent with a mock model + agent = HawkDoveVariableRiskAgent(1, Mock(risk_adjustment="adopt")) + # set a known risk level + agent.risk_level = 2 + # adjust wealth as if the model had run + agent.points = 20 + # set a mock neighbor with more points than current agent + neighbor = Mock(points=1500, risk_level=3) + with patch.object(HawkDoveVariableRiskAgent, "most_successful_neighbor", neighbor): + agent.adjust_risk() + # default behavior is to adopt successful risk level + assert agent.risk_level == neighbor.risk_level + + # now simulate a wealthiest neighbor with fewer points than current agent + neighbor.points = 12 + neighbor.risk_level = 3 + prev_risk_level = agent.risk_level + agent.adjust_risk() + # risk level should not be changed + assert agent.risk_level == prev_risk_level + + +def test_adjust_risk_average(): + # same as previous test, but with average risk adjustment strategy + agent = HawkDoveVariableRiskAgent(1, Mock(risk_adjustment="average")) + # set a known risk level + agent.risk_level = 2 + # adjust points as if the model had run + agent.points = 300 + # set a neighbor with more points than current agent + neighbor = Mock(points=350, risk_level=3) + with patch.object(HawkDoveVariableRiskAgent, "most_successful_neighbor", neighbor): + prev_risk_level = agent.risk_level + agent.adjust_risk() + # new risk level should be average of previous and most successful + assert agent.risk_level == round( + statistics.mean([neighbor.risk_level, prev_risk_level]) + ) From 0ec6760e8143b4741bf6e5a897fd934e3ebd3ff1 Mon Sep 17 00:00:00 2001 From: Rebecca Sutton Koeser Date: Thu, 26 Oct 2023 11:05:07 -0400 Subject: [PATCH 100/141] Add more charts to hawk/dove variable risk simulation (#37) * Preliminary work for hawk/dove adaptive risk attitudes #20 * Clean up duplicate code; add comments about adjusting parameters * Add more charts to hawk/dove variable risk simulation * Improve new hawk/dove charts * Clean up comments and plot method names per code review feedback --- simulatingrisk/hawkdovevar/app.py | 93 ++++++++++++++++++++++++++++- simulatingrisk/hawkdovevar/model.py | 13 ++-- tests/test_hawkdovevar.py | 8 +++ 3 files changed, 109 insertions(+), 5 deletions(-) diff --git a/simulatingrisk/hawkdovevar/app.py b/simulatingrisk/hawkdovevar/app.py index 346084d..0252618 100644 --- a/simulatingrisk/hawkdovevar/app.py +++ b/simulatingrisk/hawkdovevar/app.py @@ -1,5 +1,7 @@ # solara/jupyterviz app +import altair as alt from mesa.experimental import JupyterViz +import solara from simulatingrisk.hawkdovevar.model import HawkDoveVariableRiskModel @@ -32,10 +34,99 @@ } ) + +def plot_agents_by_risk(model): + """plot total number of agents for each risk attitude""" + agent_df = model.datacollector.get_agent_vars_dataframe().reset_index().dropna() + if agent_df.empty: + return + + last_step = agent_df.Step.max() + # plot current status / last round + last_round = agent_df[agent_df.Step == last_step] + # count number of agents for each status + grouped = last_round.groupby("risk_level", as_index=False).agg( + total=("AgentID", "count") + ) + + # bar chart to show number of agents for each risk attitude + # configure domain to always display all statuses; + # limit changes depending on if diagonals are included + # (NOTE: bug in mesa 2.12, checkbox param does not propagate) + bar_chart = ( + alt.Chart(grouped) + .mark_bar(width=15) + .encode( + x=alt.X( + "risk_level", + title="risk attitude", + # don't display any 0.5 ticks when max is 4 + axis=alt.Axis(tickCount=model.num_neighbors + 1), + scale=alt.Scale(domain=[0, model.num_neighbors]), + ), + y=alt.Y("total", title="Number of agents"), + ) + ) + return solara.FigureAltair(bar_chart) + + +def plot_hawks_by_risk(model): + """plot rolling mean of percent of agents in each risk attitude + who chose hawk over last several rounds""" + + # in the first round, mesa returns a dataframe full of NAs; ignore that + agent_df = ( + model.datacollector.get_agent_vars_dataframe() + .reset_index() + .dropna(subset=["AgentID"]) + ) + if agent_df.empty: + return + + last_step = agent_df.Step.max() + # limit to last N rounds (how many ?) + last_n_rounds = agent_df[agent_df.Step.gt(last_step - 60)].copy() + last_n_rounds["hawk"] = last_n_rounds.choice.apply( + lambda x: 1 if x == "hawk" else 0 + ) + # for each step and risk level, get number of agents and number of hawks + grouped = ( + last_n_rounds.groupby(["Step", "risk_level"], as_index=False) + .agg(hawk=("hawk", "sum"), agents=("AgentID", "count")) + .sort_values("Step") + ) + # calculate percent hawk within each group + grouped["percent_hawk"] = grouped.apply(lambda row: row.hawk / row.agents, axis=1) + # now calculate rolling percent within each risk attitude + # thanks to https://stackoverflow.com/a/53339204 + grouped["rolling_pct_hawk"] = grouped.groupby("risk_level")[ + "percent_hawk" + ].transform(lambda x: x.rolling(15, 1).mean()) + + # starting domain 0-50 so it doesn't jump / expand as much + max_step = max(last_step or 0, 50) + min_step = max(max_step - 50, 0) + + chart = ( + alt.Chart(grouped[grouped.Step.gt(min_step - 1)]) + .mark_line() + .encode( + x=alt.X("Step", scale=alt.Scale(domain=[min_step, max_step])), + y=alt.Y( + "rolling_pct_hawk", + title="rolling % hawk", + scale=alt.Scale(domain=[0, 1]), + ), + color=alt.Color("risk_level:N"), + ) + ) + return solara.FigureAltair(chart) + + page = JupyterViz( HawkDoveVariableRiskModel, jupyterviz_params_var, - measures=[plot_hawks], + measures=[plot_hawks, plot_agents_by_risk, plot_hawks_by_risk], name="Hawk/Dove game with variable risk attitudes", agent_portrayal=agent_portrayal, space_drawer=draw_hawkdove_agent_space, diff --git a/simulatingrisk/hawkdovevar/model.py b/simulatingrisk/hawkdovevar/model.py index 31d481e..bd39c75 100644 --- a/simulatingrisk/hawkdovevar/model.py +++ b/simulatingrisk/hawkdovevar/model.py @@ -13,9 +13,9 @@ class HawkDoveVariableRiskAgent(HawkDoveAgent): def set_risk_level(self): # risk level is based partially on neighborhood size, # which is configurable at the model level - num_neighbors = 8 if self.model.include_diagonals else 4 - # generate a random risk level - self.risk_level = self.random.randint(0, num_neighbors) + + # generate a random risk level between zero and number of neighbors + self.risk_level = self.random.randint(0, self.model.num_neighbors) def play(self): super().play() @@ -93,10 +93,15 @@ def __init__( f"Unsupported risk adjustment '{risk_adjustment}'; " + f"must be one of {risk_adjust_opts}" ) - self.risk_adjustment = risk_adjustment self.adjust_round_n = adjust_every + @property + def num_neighbors(self) -> int: + # number of neighbors for each agent - depends on whether + # diagonals are included or not + return 8 if self.include_diagonals else 4 + @property def adjustment_round(self) -> bool: """is the current round an adjustment round?""" diff --git a/tests/test_hawkdovevar.py b/tests/test_hawkdovevar.py index b6b98bb..27788ed 100644 --- a/tests/test_hawkdovevar.py +++ b/tests/test_hawkdovevar.py @@ -41,6 +41,14 @@ def test_init(): HawkDoveVariableRiskModel(3, risk_adjustment="bogus") +def test_num_neighbors(): + with_diagonals = HawkDoveVariableRiskModel(3) + assert with_diagonals.num_neighbors == 8 + + no_diagonals = HawkDoveVariableRiskModel(3, include_diagonals=False) + assert no_diagonals.num_neighbors == 4 + + def test_init_variable_risk_level(): model = HawkDoveVariableRiskModel( 5, From 9869fa27b814fbcd13f1c07dd5b252d7fb54c78b Mon Sep 17 00:00:00 2001 From: Rebecca Sutton Koeser Date: Tue, 7 Nov 2023 14:41:27 -0500 Subject: [PATCH 101/141] address hawk/dove last choice sync error (#41) * Allow extending data collection options; round for convergence check * Implement population risk category logic from @LaraBuchak * Set last choice after play; avoid updating while other agents choose * Require mesa version 2.1.2 --- pyproject.toml | 4 +- simulatingrisk/hawkdove/model.py | 27 ++++++---- simulatingrisk/hawkdovevar/model.py | 76 +++++++++++++++++++++++++++++ tests/test_hawkdove.py | 23 +++++++-- 4 files changed, 114 insertions(+), 16 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8e508a2..c140f63 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,8 +11,8 @@ classifiers = [ "Programming Language :: Python :: 3", ] dependencies = [ - # "mesa>=2.1", - "mesa @ git+https://github.com/projectmesa/mesa.git@main", + "mesa>=2.1.2", + # "mesa @ git+https://github.com/projectmesa/mesa.git@main", "matplotlib", "altair>5.0.1" ] diff --git a/simulatingrisk/hawkdove/model.py b/simulatingrisk/hawkdove/model.py index fdb57f9..c6a05ee 100644 --- a/simulatingrisk/hawkdove/model.py +++ b/simulatingrisk/hawkdove/model.py @@ -83,9 +83,6 @@ def choose(self): "decide what to play this round" # after the first round, choose based on what neighbors did last time if self.model.schedule.steps > 0: - # store previous choice - self.last_choice = self.choice - # choose based on the number of neighbors who played # dove last round and agent risk level @@ -106,6 +103,9 @@ def play(self): # update total points based on payoff this round self.points += payoff + # store this round's choice as previous choice + self.last_choice = self.choice + def payoff(self, other): """ If I play HAWK and neighbor plays DOVE: 3 @@ -186,18 +186,23 @@ def __init__( self.schedule.add(agent) self.grid.move_to_empty(agent) - self.datacollector = mesa.DataCollector( - model_reporters={ + self.datacollector = mesa.DataCollector(**self.get_data_collector_options()) + + def get_data_collector_options(self): + # method to return options for data collection, + # so subclasses can modify + return { + "model_reporters": { "max_agent_points": "max_agent_points", "percent_hawk": "percent_hawk", "rolling_percent_hawk": "rolling_percent_hawk", }, - agent_reporters={ + "agent_reporters": { "risk_level": "risk_level", "choice": "choice_label", "points": "points", }, - ) + } def new_agent_options(self): # generate and return a dictionary with common options @@ -214,7 +219,7 @@ def step(self): self.running = False print( f"Stopping after {self.schedule.steps} rounds. " - + f"Final rolling average % hawk: {self.rolling_percent_hawk}" + + f"Final rolling average % hawk: {round(self.rolling_percent_hawk, 2)}" ) @property @@ -247,9 +252,13 @@ def converged(self): # within our rolling window, return true # - currently checking for single value; # could allow for a small amount variation if necessary + + # in variable risk with risk adjustment, numbers are not strictly equal + # but do get close and fairly stable; round to two digits before comparing + rounded_set = set([round(x, 2) for x in self.recent_rolling_percent_hawk]) return ( len(self.recent_rolling_percent_hawk) > self.min_window - and len(set(self.recent_rolling_percent_hawk)) == 1 + and len(rounded_set) == 1 ) diff --git a/simulatingrisk/hawkdovevar/model.py b/simulatingrisk/hawkdovevar/model.py index bd39c75..bebadec 100644 --- a/simulatingrisk/hawkdovevar/model.py +++ b/simulatingrisk/hawkdovevar/model.py @@ -1,4 +1,5 @@ import statistics +from collections import Counter from simulatingrisk.hawkdove.model import HawkDoveModel, HawkDoveAgent @@ -113,3 +114,78 @@ def adjustment_round(self) -> bool: and self.schedule.steps > 0 and self.schedule.steps % self.adjust_round_n == 0 ) + + def get_data_collector_options(self): + # in addition to common hawk/dove data points, + # we want to include population risk category + opts = super().get_data_collector_options() + opts["model_reporters"]["population_risk_category"] = "population_risk_category" + return opts + + @property + def population_risk_category(self): + # calculate a category of risk distribution for the population + # based on the proportion of agents in different risk categories + # (categorization scheme defined by LB) + + # tally the number of agents with each risk level + risk_counts = Counter([a.risk_level for a in self.schedule.agents]) + # count the number of agents in three groups: + # Risk-inclined (RI) : r = 0, 1, 2 + # Risk-moderate (RM): r = 3, 4, 5 + # Risk-avoidant (RA): r = 6, 7, 8 + total = { + "risk_inclined": risk_counts[0] + risk_counts[1] + risk_counts[2], + "risk_moderate": risk_counts[3] + risk_counts[4] + risk_counts[5], + "risk_avoidant": risk_counts[6] + risk_counts[7] + risk_counts[8], + } + # for each group, calculate percent of agents in that category + total_agents = len(self.schedule.agents) + percent = {key: val / total_agents for key, val in total.items()} + + # majority risk inclined (> 50%) + if percent["risk_inclined"] > 0.5: + # If < 10% are RM & < 10% are RA: let c = 1 + if percent["risk_moderate"] < 0.1 and percent["risk_avoidant"] < 0.1: + return 1 + # If > 10% are RM & < 10% are RA: let c = 2 + if percent["risk_moderate"] > 0.1 and percent["risk_avoidant"] < 0.1: + return 2 + # If > 10% are RM & > 10% are RA: let c = 3 + if percent["risk_moderate"] > 0.1 and percent["risk_avoidant"] > 0.1: + return 3 + # If < 10% are RM & > 10% are RA: let c = 4 + if percent["risk_moderate"] < 0.1 and percent["risk_avoidant"] > 0.1: + return 4 + + # majority risk moderate + if percent["risk_moderate"] > 0.5: + # If < 10% are RI & < 10% are RA: let c = 7 + if percent["risk_inclined"] < 0.1 and percent["risk_avoidant"] < 0.1: + return 7 + # If > 10% are RI & < 10% are RA: let c = 5 + if percent["risk_inclined"] > 0.1 and percent["risk_avoidant"] < 0.1: + return 5 + # If > 10% are RI & > 10% are RA: let c = 6 + if percent["risk_inclined"] > 0.1 and percent["risk_avoidant"] > 0.1: + return 6 + # If < 10% are RI & > 10% are RA: let c = 8 + if percent["risk_inclined"] < 0.1 and percent["risk_avoidant"] > 0.1: + return 8 + + # majority risk avoidant + if percent["risk_avoidant"] > 0.5: + # If < 10% are RM & < 10% are RI: let c = 12 + if percent["risk_moderate"] < 0.1 and percent["risk_inclined"] < 0.1: + return 12 + # If > 10% are RM & < 10% are RI: let c = 11 + if percent["risk_moderate"] > 0.1 and percent["risk_inclined"] < 0.1: + return 11 + # If > 10% are RM & > 10% are RI: let c = 10 + if percent["risk_moderate"] > 0.1 and percent["risk_inclined"] > 0.1: + return 10 + # If < 10% are RM & > 10% are RI: let c = 9 + if percent["risk_moderate"] < 0.1 and percent["risk_inclined"] > 0.1: + return 9 + + return 13 diff --git a/tests/test_hawkdove.py b/tests/test_hawkdove.py index d4ed76f..17298bd 100644 --- a/tests/test_hawkdove.py +++ b/tests/test_hawkdove.py @@ -108,7 +108,6 @@ def test_agent_choose(): # on the first round, nothing should happen (uses initial choice) agent.model.schedule.steps = 0 agent.choose() - assert agent.last_choice is None # on subsequent rounds, choose based on neighbors and risk level agent.model.schedule.steps = 1 @@ -137,10 +136,24 @@ def test_agent_choose(): agent.choose() assert agent.choice == Play.DOVE - # test last choice is updated when choose runs - agent.choice = "foo" # set to confirm stored - agent.choose() - assert agent.last_choice == "foo" + +def test_agent_play(): + agent = HawkDoveSingleRiskAgent(1, Mock(agent_risk_level=3)) + # on the first round, last choice should be unset + assert agent.last_choice is None + assert agent.points == 0 + + # set initial choice and supply mock neighbors + # so we can test expected results + agent.choice = Play.HAWK + neighbor_hawk = Mock(choice=Play.HAWK) + neighbor_dove = Mock(choice=Play.DOVE) + with patch.object(HawkDoveAgent, "neighbors", [neighbor_hawk, neighbor_dove]): + agent.play() + # should get 3 points against dove and 0 against the hawk + assert agent.points == 3 + 0 + # should store current choice for next round + assert agent.last_choice == Play.HAWK def test_agent_payoff(): From dba6df3aabe3da3c163e7ac65e3823fd5dbad05f Mon Sep 17 00:00:00 2001 From: Rebecca Sutton Koeser Date: Tue, 7 Nov 2023 15:35:42 -0500 Subject: [PATCH 102/141] implement logic for population risk states (#39) * Allow extending data collection options; round for convergence check * Implement population risk category logic from @LaraBuchak * Adjust batch run settings for hawk/dove variable risk (adopt, 100 runs) * Analyze hawk/dove batch run population risk categories * Always display all risk levels in aggregate charts * Improve population risk category handling and labeling --- ...hawkdovevar_population_risk_category.ipynb | 1328 +++++++++++++++++ simulatingrisk/batch_run.py | 7 +- simulatingrisk/hawkdovevar/model.py | 65 +- tests/test_hawkdovevar.py | 32 + 4 files changed, 1416 insertions(+), 16 deletions(-) create mode 100644 notebooks/hawkdovevar_population_risk_category.ipynb diff --git a/notebooks/hawkdovevar_population_risk_category.ipynb b/notebooks/hawkdovevar_population_risk_category.ipynb new file mode 100644 index 0000000..97fb001 --- /dev/null +++ b/notebooks/hawkdovevar_population_risk_category.ipynb @@ -0,0 +1,1328 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 61, + "id": "1598f117-a674-4394-9b95-14204aa04754", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "87ceec26-3031-43c4-bbb8-c5106a4bb30b", + "metadata": {}, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'pd' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[1], line 6\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# hawk/dove variable risk with risk attitude adoption and population risk category\u001b[39;00m\n\u001b[1;32m 2\u001b[0m \u001b[38;5;66;03m#df = pd.read_csv(\"../hawkdove_variable_2023-10-26T145208_308908.csv\") # 10 runs\u001b[39;00m\n\u001b[1;32m 3\u001b[0m \u001b[38;5;66;03m#df = pd.read_csv(\"../hawkdove_variable_2023-10-26T153049_679468.csv\") # 30 runs\u001b[39;00m\n\u001b[1;32m 4\u001b[0m \u001b[38;5;66;03m#df = pd.read_csv(\"../hawkdove_variable_2023-10-26T153455_475059.csv\") # 50 runs\u001b[39;00m\n\u001b[0;32m----> 6\u001b[0m df \u001b[38;5;241m=\u001b[39m \u001b[43mpd\u001b[49m\u001b[38;5;241m.\u001b[39mread_csv(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m../data/hawkdove_variable_2023-10-26T154836_183962.csv\u001b[39m\u001b[38;5;124m\"\u001b[39m) \u001b[38;5;66;03m# 100 runs\u001b[39;00m\n", + "\u001b[0;31mNameError\u001b[0m: name 'pd' is not defined" + ] + } + ], + "source": [ + "# hawk/dove variable risk with risk attitude adoption and population risk category\n", + "#df = pd.read_csv(\"../hawkdove_variable_2023-10-26T145208_308908.csv\") # 10 runs\n", + "#df = pd.read_csv(\"../hawkdove_variable_2023-10-26T153049_679468.csv\") # 30 runs\n", + "#df = pd.read_csv(\"../hawkdove_variable_2023-10-26T153455_475059.csv\") # 50 runs\n", + "\n", + "df = pd.read_csv(\"../data/hawkdove_variable_2023-10-26T154836_183962.csv\") # 100 runs\n" + ] + }, + { + "cell_type": "code", + "execution_count": 163, + "id": "723bf9f1-ae50-4789-9e5f-61492982c2cc", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
RunIditerationStepgrid_sizerisk_adjustmentmax_agent_pointspercent_hawkrolling_percent_hawkpopulation_risk_categoryAgentIDrisk_levelchoicepoints
000010adopt24.00.47NaN13NaNNaNNaNNaN
100110adopt39.00.22NaN130.02.0dove15.7
200110adopt39.00.22NaN131.05.0dove15.7
300110adopt39.00.22NaN132.00.0hawk9.0
400110adopt39.00.22NaN133.08.0dove14.6
..........................................
119039599997810adopt1054.40.310.519595.02.0hawk881.7
119039699997810adopt1054.40.310.519596.01.0hawk791.6
119039799997810adopt1054.40.310.519597.02.0hawk864.2
119039899997810adopt1054.40.310.519598.03.0hawk861.3
119039999997810adopt1054.40.310.519599.03.0hawk679.5
\n", + "

1190400 rows × 13 columns

\n", + "
" + ], + "text/plain": [ + " RunId iteration Step grid_size risk_adjustment max_agent_points \n", + "0 0 0 0 10 adopt 24.0 \\\n", + "1 0 0 1 10 adopt 39.0 \n", + "2 0 0 1 10 adopt 39.0 \n", + "3 0 0 1 10 adopt 39.0 \n", + "4 0 0 1 10 adopt 39.0 \n", + "... ... ... ... ... ... ... \n", + "1190395 99 99 78 10 adopt 1054.4 \n", + "1190396 99 99 78 10 adopt 1054.4 \n", + "1190397 99 99 78 10 adopt 1054.4 \n", + "1190398 99 99 78 10 adopt 1054.4 \n", + "1190399 99 99 78 10 adopt 1054.4 \n", + "\n", + " percent_hawk rolling_percent_hawk population_risk_category \n", + "0 0.47 NaN 13 \\\n", + "1 0.22 NaN 13 \n", + "2 0.22 NaN 13 \n", + "3 0.22 NaN 13 \n", + "4 0.22 NaN 13 \n", + "... ... ... ... \n", + "1190395 0.31 0.519 5 \n", + "1190396 0.31 0.519 5 \n", + "1190397 0.31 0.519 5 \n", + "1190398 0.31 0.519 5 \n", + "1190399 0.31 0.519 5 \n", + "\n", + " AgentID risk_level choice points \n", + "0 NaN NaN NaN NaN \n", + "1 0.0 2.0 dove 15.7 \n", + "2 1.0 5.0 dove 15.7 \n", + "3 2.0 0.0 hawk 9.0 \n", + "4 3.0 8.0 dove 14.6 \n", + "... ... ... ... ... \n", + "1190395 95.0 2.0 hawk 881.7 \n", + "1190396 96.0 1.0 hawk 791.6 \n", + "1190397 97.0 2.0 hawk 864.2 \n", + "1190398 98.0 3.0 hawk 861.3 \n", + "1190399 99.0 3.0 hawk 679.5 \n", + "\n", + "[1190400 rows x 13 columns]" + ] + }, + "execution_count": 163, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df" + ] + }, + { + "cell_type": "code", + "execution_count": 164, + "id": "908728b0-3fae-4721-94f7-13d104050a0e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
RunIditerationSteppercent_hawkrolling_percent_hawkpopulation_risk_category
00000.47NaN13
10010.22NaN13
1010020.64NaN13
2010030.53NaN13
3010040.32NaN13
.....................
11899009999740.980.5203335
11900009999750.310.5193335
11901009999760.260.5196675
11902009999770.980.5200005
11903009999780.310.5190005
\n", + "

12003 rows × 6 columns

\n", + "
" + ], + "text/plain": [ + " RunId iteration Step percent_hawk rolling_percent_hawk \n", + "0 0 0 0 0.47 NaN \\\n", + "1 0 0 1 0.22 NaN \n", + "101 0 0 2 0.64 NaN \n", + "201 0 0 3 0.53 NaN \n", + "301 0 0 4 0.32 NaN \n", + "... ... ... ... ... ... \n", + "1189900 99 99 74 0.98 0.520333 \n", + "1190000 99 99 75 0.31 0.519333 \n", + "1190100 99 99 76 0.26 0.519667 \n", + "1190200 99 99 77 0.98 0.520000 \n", + "1190300 99 99 78 0.31 0.519000 \n", + "\n", + " population_risk_category \n", + "0 13 \n", + "1 13 \n", + "101 13 \n", + "201 13 \n", + "301 13 \n", + "... ... \n", + "1189900 5 \n", + "1190000 5 \n", + "1190100 5 \n", + "1190200 5 \n", + "1190300 5 \n", + "\n", + "[12003 rows x 6 columns]" + ] + }, + "execution_count": 164, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# limit to model-only fields and drop duplicates, so we can focus on end state for population risk category\n", + "model_df = df[['RunId', 'iteration', 'Step', 'percent_hawk', 'rolling_percent_hawk', 'population_risk_category']].drop_duplicates()\n", + "model_df" + ] + }, + { + "cell_type": "code", + "execution_count": 165, + "id": "b4d28404-450e-4df1-98d3-651b9440464a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
RunIdlast_step
9090223
919197
9292128
9393112
9494177
959574
9696112
9797134
989872
999978
\n", + "
" + ], + "text/plain": [ + " RunId last_step\n", + "90 90 223\n", + "91 91 97\n", + "92 92 128\n", + "93 93 112\n", + "94 94 177\n", + "95 95 74\n", + "96 96 112\n", + "97 97 134\n", + "98 98 72\n", + "99 99 78" + ] + }, + "execution_count": 165, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# how long did the simulation run each time?\n", + "# batch run was set to step at 200; less than that means convergence logic stopped it early\n", + "last_step = model_df.groupby('RunId', as_index=False).agg(last_step=('Step', 'max'))\n", + "last_step.tail(10)" + ] + }, + { + "cell_type": "code", + "execution_count": 166, + "id": "6b7e96b7-b887-4821-84be-ca2cce48fdb3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
RunIditerationSteppercent_hawkrolling_percent_hawkpopulation_risk_categorylast_step
9000900.510.541667290
200111090.220.5416675109
28622850.130.559333285
414331270.140.44066713127
50144860.900.547667286
639551370.950.4506678137
73566950.810.565667295
912771760.910.46100013176
1043881300.140.5456675130
114399990.600.527667599
\n", + "
" + ], + "text/plain": [ + " RunId iteration Step percent_hawk rolling_percent_hawk \n", + "90 0 0 90 0.51 0.541667 \\\n", + "200 1 1 109 0.22 0.541667 \n", + "286 2 2 85 0.13 0.559333 \n", + "414 3 3 127 0.14 0.440667 \n", + "501 4 4 86 0.90 0.547667 \n", + "639 5 5 137 0.95 0.450667 \n", + "735 6 6 95 0.81 0.565667 \n", + "912 7 7 176 0.91 0.461000 \n", + "1043 8 8 130 0.14 0.545667 \n", + "1143 9 9 99 0.60 0.527667 \n", + "\n", + " population_risk_category last_step \n", + "90 2 90 \n", + "200 5 109 \n", + "286 2 85 \n", + "414 13 127 \n", + "501 2 86 \n", + "639 8 137 \n", + "735 2 95 \n", + "912 13 176 \n", + "1043 5 130 \n", + "1143 5 99 " + ] + }, + "execution_count": 166, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# what does the risk distribution look like at the end of each run?\n", + "\n", + "# merge with last step and then filter to just the last step from each run\n", + "merged = model_df.merge(last_step, on='RunId')\n", + "model_last_step = merged[merged.Step == merged.last_step]\n", + "model_last_step.head(10)" + ] + }, + { + "cell_type": "code", + "execution_count": 167, + "id": "ec60c232-bbb2-47e3-b374-a08aa39ac353", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "count 100.000000\n", + "mean 6.000000\n", + "std 3.887301\n", + "min 2.000000\n", + "25% 2.000000\n", + "50% 5.000000\n", + "75% 9.000000\n", + "max 13.000000\n", + "Name: population_risk_category, dtype: float64" + ] + }, + "execution_count": 167, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model_last_step.population_risk_category.describe()" + ] + }, + { + "cell_type": "code", + "execution_count": 168, + "id": "a244aba6-eb90-481a-b906-f6b54b1e3a00", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 2, 5, 13, 8, 3, 6, 9, 12, 10, 7])" + ] + }, + "execution_count": 168, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "model_last_step.population_risk_category.unique()" + ] + }, + { + "cell_type": "code", + "execution_count": 169, + "id": "f18ef78b-22d6-4a7e-b0de-279f47d0a1eb", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
risk_categoryruns
0230
131
2536
363
472
581
695
7104
8122
91316
\n", + "
" + ], + "text/plain": [ + " risk_category runs\n", + "0 2 30\n", + "1 3 1\n", + "2 5 36\n", + "3 6 3\n", + "4 7 2\n", + "5 8 1\n", + "6 9 5\n", + "7 10 4\n", + "8 12 2\n", + "9 13 16" + ] + }, + "execution_count": 169, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# graph number of runs in each category\n", + "\n", + "model_grouped = model_last_step.groupby('population_risk_category', as_index=False).agg(runs=('RunId', 'count'))\n", + "model_grouped.rename(columns={'population_risk_category': 'risk_category'}, inplace=True) \n", + "model_grouped" + ] + }, + { + "cell_type": "code", + "execution_count": 170, + "id": "d540677f-3303-401f-b2fb-b3134750902f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
risk_categoryrunscategory_label
0230majority risk inclined
131majority risk inclined
2536majority risk moderate
363majority risk moderate
472majority risk moderate
581majority risk moderate
695majority risk avoidant
7104majority risk avoidant
8122majority risk avoidant
91316no majority
\n", + "
" + ], + "text/plain": [ + " risk_category runs category_label\n", + "0 2 30 majority risk inclined\n", + "1 3 1 majority risk inclined\n", + "2 5 36 majority risk moderate\n", + "3 6 3 majority risk moderate\n", + "4 7 2 majority risk moderate\n", + "5 8 1 majority risk moderate\n", + "6 9 5 majority risk avoidant\n", + "7 10 4 majority risk avoidant\n", + "8 12 2 majority risk avoidant\n", + "9 13 16 no majority" + ] + }, + "execution_count": 170, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# set labels to group risk categories\n", + "def category_label(c):\n", + " if c in [1,2,3,4]:\n", + " return 'majority risk inclined'\n", + " if c in [5,6,7,8]:\n", + " return 'majority risk moderate'\n", + " if c in [9,10,11,12]:\n", + " return 'majority risk avoidant'\n", + " return 'no majority'\n", + "\n", + "model_grouped['category_label'] = model_grouped.risk_category.apply(category_label)\n", + "model_grouped" + ] + }, + { + "cell_type": "code", + "execution_count": 171, + "id": "06d66111-75cc-4d12-80ea-39d251295b32", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 171, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import altair as alt\n", + "alt.Chart(model_grouped).mark_bar(width=15).encode(\n", + " x=alt.X(\"risk_category\", title=\"risk category\", axis=alt.Axis(tickCount=13), # 13 categories\n", + " scale=alt.Scale(domain=[1, 13])),\n", + " y=alt.Y(\"runs\", title=\"Number of runs\"),\n", + " color=alt.Color(\"category_label\", title=\"type\")\n", + ").properties(title='Distribution of runs by final population risk category')" + ] + }, + { + "cell_type": "code", + "execution_count": 174, + "id": "39fc4018-0d1f-4158-a7a6-f5ccb764eec0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.VConcatChart(...)" + ] + }, + "execution_count": 174, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# graph some some sample risk distributions within each category, to help understand\n", + "\n", + "combined_chart = None\n", + "\n", + "for category in sorted(model_last_step.population_risk_category.unique()):\n", + " # how many runs ended in this category?\n", + " runs = model_last_step[model_last_step.population_risk_category == category]\n", + " num_runs = len(runs)\n", + "\n", + " agent_data = []\n", + " # display at max 5 runs for each category\n", + " for run in runs.head(5).itertuples():\n", + " # get agent data from original df for this run and step\n", + " run_agent_df = df[(df.RunId == run.RunId) & (df.Step == run.Step)]\n", + " # group and calculate number of agents per risk level\n", + " grouped = run_agent_df.groupby(\"risk_level\", as_index=False).agg(total=(\"AgentID\", \"count\"))\n", + " grouped['RunId'] = run.RunId # set run id for graphing as columned bar chart\n", + " agent_data.append(grouped)\n", + "\n", + " # combine collected agent data for all runs in this category\n", + " agent_df = pd.concat(agent_data)\n", + "\n", + " # column bar chart adapted from https://stackoverflow.com/a/71608013\n", + " chart = alt.Chart(agent_df).mark_bar(width=10).encode(\n", + " alt.X('risk_level', title='risk attitude', axis=alt.Axis(tickCount=9), scale=alt.Scale(domain=[0, 8])),\n", + " alt.Y('total'),\n", + " alt.Column('RunId', header=alt.Header(title=f\"Category {category} ({num_runs} run{'' if num_runs == 1 else 's'})\"))\n", + " ).properties(\n", + " width=200,\n", + " height=200\n", + " )\n", + "\n", + " # concatenate category charts vertically\n", + " if combined_chart is None:\n", + " combined_chart = chart\n", + " else:\n", + " combined_chart &= chart\n", + " \n", + "combined_chart" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/simulatingrisk/batch_run.py b/simulatingrisk/batch_run.py index 98c7be7..fce64c7 100755 --- a/simulatingrisk/batch_run.py +++ b/simulatingrisk/batch_run.py @@ -78,9 +78,10 @@ def hawkdove_singlerisk_batch_run(args): def hawkdove_variablerisk_batch_run(args): params = { - "grid_size": 20, + "grid_size": 10, + "risk_adjustment": "adopt", # run adopt only for now } - iterations = 5 + iterations = 100 results = batch_run( HawkDoveVariableRiskModel, parameters=params, @@ -88,7 +89,7 @@ def hawkdove_variablerisk_batch_run(args): number_processes=1, data_collection_period=1, display_progress=True, - max_steps=200, # converges very quickly, so don't run 1000 times + max_steps=250, # converges fairly quickly, don't run 1000 times ) # include the mode in the output filename save_results("hawkdove_variable", results) diff --git a/simulatingrisk/hawkdovevar/model.py b/simulatingrisk/hawkdovevar/model.py index bebadec..63b69be 100644 --- a/simulatingrisk/hawkdovevar/model.py +++ b/simulatingrisk/hawkdovevar/model.py @@ -1,5 +1,6 @@ import statistics from collections import Counter +from enum import Enum from simulatingrisk.hawkdove.model import HawkDoveModel, HawkDoveAgent @@ -52,6 +53,44 @@ def adjust_risk(self): ) +class RiskState(Enum): + """Categorization of population risk states""" + + # majority risk inclined + c1 = 1 + c2 = 2 + c3 = 3 + c4 = 4 + + # majority risk moderate + c5 = 5 + c6 = 6 + c7 = 7 + c8 = 8 + + # majority risk avoidant + c9 = 9 + c10 = 10 + c11 = 11 + c12 = 12 + + # no clear majority + c13 = 13 + + @classmethod + def category(cls, val): + # handle both integer and risk state enum value + if isinstance(val, RiskState): + val = val.value + if val in {1, 2, 3, 4}: + return "majority risk inclined" + if val in {5, 6, 7, 8}: + return "majority risk moderate" + if val in {9, 10, 11, 12}: + return "majority risk avoidant" + return "no majority" + + class HawkDoveVariableRiskModel(HawkDoveModel): """ Model for hawk/dove game with variable risk attitudes. @@ -147,45 +186,45 @@ def population_risk_category(self): if percent["risk_inclined"] > 0.5: # If < 10% are RM & < 10% are RA: let c = 1 if percent["risk_moderate"] < 0.1 and percent["risk_avoidant"] < 0.1: - return 1 + return RiskState.c1 # If > 10% are RM & < 10% are RA: let c = 2 if percent["risk_moderate"] > 0.1 and percent["risk_avoidant"] < 0.1: - return 2 + return RiskState.c2 # If > 10% are RM & > 10% are RA: let c = 3 if percent["risk_moderate"] > 0.1 and percent["risk_avoidant"] > 0.1: - return 3 + return RiskState.c3 # If < 10% are RM & > 10% are RA: let c = 4 if percent["risk_moderate"] < 0.1 and percent["risk_avoidant"] > 0.1: - return 4 + return RiskState.c4 # majority risk moderate if percent["risk_moderate"] > 0.5: # If < 10% are RI & < 10% are RA: let c = 7 if percent["risk_inclined"] < 0.1 and percent["risk_avoidant"] < 0.1: - return 7 + return RiskState.c7 # If > 10% are RI & < 10% are RA: let c = 5 if percent["risk_inclined"] > 0.1 and percent["risk_avoidant"] < 0.1: - return 5 + return RiskState.c5 # If > 10% are RI & > 10% are RA: let c = 6 if percent["risk_inclined"] > 0.1 and percent["risk_avoidant"] > 0.1: - return 6 + return RiskState.c6 # If < 10% are RI & > 10% are RA: let c = 8 if percent["risk_inclined"] < 0.1 and percent["risk_avoidant"] > 0.1: - return 8 + return RiskState.c8 # majority risk avoidant if percent["risk_avoidant"] > 0.5: # If < 10% are RM & < 10% are RI: let c = 12 if percent["risk_moderate"] < 0.1 and percent["risk_inclined"] < 0.1: - return 12 + return RiskState.c12 # If > 10% are RM & < 10% are RI: let c = 11 if percent["risk_moderate"] > 0.1 and percent["risk_inclined"] < 0.1: - return 11 + return RiskState.c11 # If > 10% are RM & > 10% are RI: let c = 10 if percent["risk_moderate"] > 0.1 and percent["risk_inclined"] > 0.1: - return 10 + return RiskState.c10 # If < 10% are RM & > 10% are RI: let c = 9 if percent["risk_moderate"] < 0.1 and percent["risk_inclined"] > 0.1: - return 9 + return RiskState.c9 - return 13 + return RiskState.c13 diff --git a/tests/test_hawkdovevar.py b/tests/test_hawkdovevar.py index 27788ed..7796d69 100644 --- a/tests/test_hawkdovevar.py +++ b/tests/test_hawkdovevar.py @@ -6,6 +6,7 @@ from simulatingrisk.hawkdovevar.model import ( HawkDoveVariableRiskModel, HawkDoveVariableRiskAgent, + RiskState, ) @@ -84,6 +85,37 @@ def test_adjustment_round(params, expect_adjust_step): assert not model.adjustment_round +def test_population_risk_category(): + model = HawkDoveVariableRiskModel(3) + model.schedule = Mock() + + # majority risk inclined + model.schedule.agents = [Mock(risk_level=0), Mock(risk_level=1), Mock(risk_level=2)] + assert model.population_risk_category == RiskState.c1 + # three risk-inclined agents and one risk moderate + model.schedule.agents.append(Mock(risk_level=4)) + assert model.population_risk_category == RiskState.c2 + + # majority risk moderate + model.schedule.agents = [Mock(risk_level=4), Mock(risk_level=3), Mock(risk_level=5)] + assert model.population_risk_category == RiskState.c7 + + # majority risk avoidant + model.schedule.agents = [Mock(risk_level=6), Mock(risk_level=7), Mock(risk_level=8)] + assert model.population_risk_category == RiskState.c12 + + +def test_riskstate_label(): + # enum value or integer value + assert RiskState.category(RiskState.c1) == "majority risk inclined" + assert RiskState.category(2) == "majority risk inclined" + assert RiskState.category(RiskState.c5) == "majority risk moderate" + assert RiskState.category(6) == "majority risk moderate" + assert RiskState.category(RiskState.c11) == "majority risk avoidant" + assert RiskState.category(RiskState.c13) == "no majority" + assert RiskState.category(13) == "no majority" + + def test_most_successful_neighbor(): # initialize an agent with a mock model agent = HawkDoveVariableRiskAgent(1, Mock(), 1000) From c014969d3b8b470d02d312dc94cb42256b758322 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 7 Nov 2023 15:38:09 -0500 Subject: [PATCH 103/141] Updated population risk state notebook using new enum & label method --- ...hawkdovevar_population_risk_category.ipynb | 96 ++++++++----------- 1 file changed, 38 insertions(+), 58 deletions(-) diff --git a/notebooks/hawkdovevar_population_risk_category.ipynb b/notebooks/hawkdovevar_population_risk_category.ipynb index 97fb001..66d5eee 100644 --- a/notebooks/hawkdovevar_population_risk_category.ipynb +++ b/notebooks/hawkdovevar_population_risk_category.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 61, + "execution_count": 2, "id": "1598f117-a674-4394-9b95-14204aa04754", "metadata": {}, "outputs": [], @@ -12,34 +12,22 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 4, "id": "87ceec26-3031-43c4-bbb8-c5106a4bb30b", "metadata": {}, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'pd' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[1], line 6\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# hawk/dove variable risk with risk attitude adoption and population risk category\u001b[39;00m\n\u001b[1;32m 2\u001b[0m \u001b[38;5;66;03m#df = pd.read_csv(\"../hawkdove_variable_2023-10-26T145208_308908.csv\") # 10 runs\u001b[39;00m\n\u001b[1;32m 3\u001b[0m \u001b[38;5;66;03m#df = pd.read_csv(\"../hawkdove_variable_2023-10-26T153049_679468.csv\") # 30 runs\u001b[39;00m\n\u001b[1;32m 4\u001b[0m \u001b[38;5;66;03m#df = pd.read_csv(\"../hawkdove_variable_2023-10-26T153455_475059.csv\") # 50 runs\u001b[39;00m\n\u001b[0;32m----> 6\u001b[0m df \u001b[38;5;241m=\u001b[39m \u001b[43mpd\u001b[49m\u001b[38;5;241m.\u001b[39mread_csv(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m../data/hawkdove_variable_2023-10-26T154836_183962.csv\u001b[39m\u001b[38;5;124m\"\u001b[39m) \u001b[38;5;66;03m# 100 runs\u001b[39;00m\n", - "\u001b[0;31mNameError\u001b[0m: name 'pd' is not defined" - ] - } - ], + "outputs": [], "source": [ "# hawk/dove variable risk with risk attitude adoption and population risk category\n", "#df = pd.read_csv(\"../hawkdove_variable_2023-10-26T145208_308908.csv\") # 10 runs\n", "#df = pd.read_csv(\"../hawkdove_variable_2023-10-26T153049_679468.csv\") # 30 runs\n", "#df = pd.read_csv(\"../hawkdove_variable_2023-10-26T153455_475059.csv\") # 50 runs\n", "\n", - "df = pd.read_csv(\"../data/hawkdove_variable_2023-10-26T154836_183962.csv\") # 100 runs\n" + "df = pd.read_csv(\"../batch-data/hawkdove_variable_2023-10-26T154836_183962.csv\") # 100 runs\n" ] }, { "cell_type": "code", - "execution_count": 163, + "execution_count": 5, "id": "723bf9f1-ae50-4789-9e5f-61492982c2cc", "metadata": {}, "outputs": [ @@ -304,7 +292,7 @@ "[1190400 rows x 13 columns]" ] }, - "execution_count": 163, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -315,7 +303,7 @@ }, { "cell_type": "code", - "execution_count": 164, + "execution_count": 6, "id": "908728b0-3fae-4721-94f7-13d104050a0e", "metadata": {}, "outputs": [ @@ -483,7 +471,7 @@ "[12003 rows x 6 columns]" ] }, - "execution_count": 164, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -496,7 +484,7 @@ }, { "cell_type": "code", - "execution_count": 165, + "execution_count": 7, "id": "b4d28404-450e-4df1-98d3-651b9440464a", "metadata": {}, "outputs": [ @@ -594,7 +582,7 @@ "99 99 78" ] }, - "execution_count": 165, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -608,7 +596,7 @@ }, { "cell_type": "code", - "execution_count": 166, + "execution_count": 8, "id": "6b7e96b7-b887-4821-84be-ca2cce48fdb3", "metadata": {}, "outputs": [ @@ -773,7 +761,7 @@ "1143 5 99 " ] }, - "execution_count": 166, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -789,7 +777,7 @@ }, { "cell_type": "code", - "execution_count": 167, + "execution_count": 9, "id": "ec60c232-bbb2-47e3-b374-a08aa39ac353", "metadata": {}, "outputs": [ @@ -807,7 +795,7 @@ "Name: population_risk_category, dtype: float64" ] }, - "execution_count": 167, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -818,7 +806,7 @@ }, { "cell_type": "code", - "execution_count": 168, + "execution_count": 10, "id": "a244aba6-eb90-481a-b906-f6b54b1e3a00", "metadata": {}, "outputs": [ @@ -828,7 +816,7 @@ "array([ 2, 5, 13, 8, 3, 6, 9, 12, 10, 7])" ] }, - "execution_count": 168, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -839,7 +827,7 @@ }, { "cell_type": "code", - "execution_count": 169, + "execution_count": 11, "id": "f18ef78b-22d6-4a7e-b0de-279f47d0a1eb", "metadata": {}, "outputs": [ @@ -937,7 +925,7 @@ "9 13 16" ] }, - "execution_count": 169, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -952,7 +940,7 @@ }, { "cell_type": "code", - "execution_count": 170, + "execution_count": 13, "id": "d540677f-3303-401f-b2fb-b3134750902f", "metadata": {}, "outputs": [ @@ -1061,29 +1049,21 @@ "9 13 16 no majority" ] }, - "execution_count": 170, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "# set labels to group risk categories\n", - "def category_label(c):\n", - " if c in [1,2,3,4]:\n", - " return 'majority risk inclined'\n", - " if c in [5,6,7,8]:\n", - " return 'majority risk moderate'\n", - " if c in [9,10,11,12]:\n", - " return 'majority risk avoidant'\n", - " return 'no majority'\n", + "from simulatingrisk.hawkdovevar.model import RiskState\n", "\n", - "model_grouped['category_label'] = model_grouped.risk_category.apply(category_label)\n", + "model_grouped['category_label'] = model_grouped.risk_category.apply(RiskState.category)\n", "model_grouped" ] }, { "cell_type": "code", - "execution_count": 171, + "execution_count": 14, "id": "06d66111-75cc-4d12-80ea-39d251295b32", "metadata": {}, "outputs": [ @@ -1092,23 +1072,23 @@ "text/html": [ "\n", "\n", - "
\n", + "
\n", "" + ], + "text/plain": [ + ":Histogram [Step] (Step_count)" + ] + }, + "execution_count": 99, + "metadata": { + "application/vnd.holoviews_exec.v0+json": { + "id": "p1330" + } + }, + "output_type": "execute_result" + } + ], + "source": [ + "converged_df[\"Step\"].plot.hist()" + ] + }, + { + "cell_type": "markdown", + "id": "d4c671c7-3cfd-4b9d-b765-1bfd94d052b0", + "metadata": {}, + "source": [ + "## compare different initial distributions" + ] + }, + { + "cell_type": "code", + "execution_count": 100, + "id": "91116b11-aa00-401a-9ab1-5de936f196e5", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "shape: (5,)
risk_distribution
str
"skewed right"
"uniform"
"skewed left"
"bimodal"
"normal"
" + ], + "text/plain": [ + "shape: (5,)\n", + "Series: 'risk_distribution' [str]\n", + "[\n", + "\t\"skewed right\"\n", + "\t\"uniform\"\n", + "\t\"skewed left\"\n", + "\t\"bimodal\"\n", + "\t\"normal\"\n", + "]" + ] + }, + "execution_count": 100, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df[\"risk_distribution\"].unique()" + ] + }, + { + "cell_type": "markdown", + "id": "23171e3e-7ec9-4a71-9ee5-aeee4bb8faaa", + "metadata": {}, + "source": [ + "How many converged runs in each subset?\n" + ] + }, + { + "cell_type": "code", + "execution_count": 101, + "id": "3d540440-e528-4535-98f3-59d051cb6e3b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "shape: (5, 2)
risk_distributioncount
stru32
"uniform"938
"skewed right"987
"bimodal"710
"normal"996
"skewed left"963
" + ], + "text/plain": [ + "shape: (5, 2)\n", + "┌───────────────────┬───────┐\n", + "│ risk_distribution ┆ count │\n", + "│ --- ┆ --- │\n", + "│ str ┆ u32 │\n", + "╞═══════════════════╪═══════╡\n", + "│ uniform ┆ 938 │\n", + "│ skewed right ┆ 987 │\n", + "│ bimodal ┆ 710 │\n", + "│ normal ┆ 996 │\n", + "│ skewed left ┆ 963 │\n", + "└───────────────────┴───────┘" + ] + }, + "execution_count": 101, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "converged_df.group_by(\"risk_distribution\").count()" + ] + }, + { + "cell_type": "code", + "execution_count": 102, + "id": "9b324c1b-26f7-45a5-95e7-a2c4bf434d13", + "metadata": {}, + "outputs": [], + "source": [ + "# filter converged run data into subsets by risk distribution\n", + "\n", + "subset = {}\n", + "\n", + "for distribution in converged_df[\"risk_distribution\"].unique():\n", + " subset[distribution] = converged_df.filter(pl.col(\"risk_distribution\") == distribution)" + ] + }, + { + "cell_type": "markdown", + "id": "dbfcdd7d-4df4-4fd7-b6d1-e1a8601f78a8", + "metadata": {}, + "source": [ + "### How does initial distribution affect convergence?" + ] + }, + { + "cell_type": "code", + "execution_count": 103, + "id": "89213472-05ca-46a1-a6b9-be0763513618", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "shape: (10, 3)
risk_distributionstatuscount
strstru32
"uniform""converged"938
"normal""converged"996
"skewed left""running"37
"normal""running"4
"skewed left""converged"963
"skewed right""running"13
"bimodal""running"290
"skewed right""converged"987
"uniform""running"62
"bimodal""converged"710
" + ], + "text/plain": [ + "shape: (10, 3)\n", + "┌───────────────────┬───────────┬───────┐\n", + "│ risk_distribution ┆ status ┆ count │\n", + "│ --- ┆ --- ┆ --- │\n", + "│ str ┆ str ┆ u32 │\n", + "╞═══════════════════╪═══════════╪═══════╡\n", + "│ uniform ┆ converged ┆ 938 │\n", + "│ normal ┆ converged ┆ 996 │\n", + "│ skewed left ┆ running ┆ 37 │\n", + "│ normal ┆ running ┆ 4 │\n", + "│ … ┆ … ┆ … │\n", + "│ bimodal ┆ running ┆ 290 │\n", + "│ skewed right ┆ converged ┆ 987 │\n", + "│ uniform ┆ running ┆ 62 │\n", + "│ bimodal ┆ converged ┆ 710 │\n", + "└───────────────────┴───────────┴───────┘" + ] + }, + "execution_count": 103, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "status_by_dist = df.group_by(\"risk_distribution\", \"status\").count()\n", + "status_by_dist" + ] + }, + { + "cell_type": "code", + "execution_count": 104, + "id": "48fd71eb-31b5-4bb6-bef0-c0a827991492", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 104, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "alt.Chart(status_by_dist).mark_bar().encode(\n", + " x='risk_distribution:N',\n", + " y='count',\n", + " color='status:N'\n", + ").properties(title=\"Simulation status (converged/running) by risk distribution\", width=250, height=400)" + ] + }, + { + "cell_type": "code", + "execution_count": 105, + "id": "50282f62-38ce-4709-9f9b-e845bca67606", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.Chart(...)" + ] + }, + "execution_count": 105, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "alt.Chart(converged_df).mark_boxplot(size=20).encode(\n", + " x='risk_distribution:N',\n", + " y='Step',\n", + ").properties(\n", + " title=alt.TitleParams(\n", + " \"Simulation run length by risk distribution\", \n", + " subtitle=\"(converged runs only)\"), \n", + " width=350, height=450)" + ] + }, + { + "cell_type": "markdown", + "id": "f02d3914-5c01-47c6-a4ff-cf43a88f2444", + "metadata": {}, + "source": [ + "### population categories by risk distribution" + ] + }, + { + "cell_type": "code", + "execution_count": 106, + "id": "8f0e0645-d71a-456f-bc96-5d38ce898062", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "
\n", + "" + ], + "text/plain": [ + "alt.HConcatChart(...)" + ] + }, + "execution_count": 106, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import altair as alt\n", + "from simulatingrisk.hawkdovemulti import analysis_utils\n", + "\n", + "\n", + "uniform_chart = analysis_utils.graph_population_risk_category(\n", + " analysis_utils.groupby_population_risk_category(subset[\"uniform\"])\n", + ").properties(title=\"risk distribution: uniform/random\")\n", + "\n", + "normal_chart = analysis_utils.graph_population_risk_category(\n", + " analysis_utils.groupby_population_risk_category(subset[\"normal\"])\n", + ").properties(title=\"risk distribution: normal\")\n", + "\n", + "bimodal_chart = analysis_utils.graph_population_risk_category(\n", + " analysis_utils.groupby_population_risk_category(subset[\"bimodal\"])\n", + ").properties(title=\"risk distribution: bimodal\")\n", + "\n", + "skewedleft_chart = analysis_utils.graph_population_risk_category(\n", + " analysis_utils.groupby_population_risk_category(subset[\"skewed left\"])\n", + ").properties(title=\"risk distribution: skewed left\")\n", + "\n", + "skewedright_chart = analysis_utils.graph_population_risk_category(\n", + " analysis_utils.groupby_population_risk_category(subset[\"skewed right\"])\n", + ").properties(title=\"risk distribution: skewed right\")\n", + "\n", + "(uniform_chart | normal_chart | bimodal_chart | skewedleft_chart | skewedright_chart) \\\n", + ".properties(title=alt.TitleParams(\"Population category by run over initial risk distributions\", anchor=\"middle\")).resolve_scale(y='shared')\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}