From 0d5980c985585e8eae41d3437d6ffd30c22cea75 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 18 Oct 2023 16:51:39 -0400 Subject: [PATCH 1/9] Preliminary work for hawk/dove adaptive risk attitudes #20 --- simulatingrisk/hawkdove/model.py | 30 ++++++++++++- simulatingrisk/hawkdovevar/app.py | 18 ++++++++ simulatingrisk/hawkdovevar/model.py | 68 ++++++++++++++++++++++++++++- tests/test_hawkdove.py | 5 ++- 4 files changed, 117 insertions(+), 4 deletions(-) diff --git a/simulatingrisk/hawkdove/model.py b/simulatingrisk/hawkdove/model.py index 0a08e9e..583373c 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 @@ -203,6 +217,18 @@ def step(self): + f"Final rolling average % hawk: {self.rolling_percent_hawk}" ) + @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 + ) + @property def max_agent_points(self): # what is the current largest point total of any agent? diff --git a/simulatingrisk/hawkdovevar/app.py b/simulatingrisk/hawkdovevar/app.py index 79bf84d..ff9abfa 100644 --- a/simulatingrisk/hawkdovevar/app.py +++ b/simulatingrisk/hawkdovevar/app.py @@ -12,6 +12,24 @@ jupyterviz_params_var = jupyterviz_params.copy() del jupyterviz_params_var["agent_risk_level"] +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": { + "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..9c02b4f 100644 --- a/simulatingrisk/hawkdovevar/model.py +++ b/simulatingrisk/hawkdovevar/model.py @@ -1,3 +1,5 @@ +import statistics + from simulatingrisk.hawkdove.model import HawkDoveModel, HawkDoveAgent @@ -13,8 +15,53 @@ 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 + 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 @@ -23,8 +70,27 @@ def __init__( 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 + 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..2b50d12 100644 --- a/tests/test_hawkdove.py +++ b/tests/test_hawkdove.py @@ -67,7 +67,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(): From e49b674aedf0f711c50aa88ef2218d257627aec1 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Thu, 19 Oct 2023 13:16:34 -0400 Subject: [PATCH 2/9] Clean up duplicate code; add comments about adjusting parameters --- simulatingrisk/hawkdove/model.py | 12 ------------ simulatingrisk/hawkdovevar/app.py | 3 +++ 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/simulatingrisk/hawkdove/model.py b/simulatingrisk/hawkdove/model.py index 583373c..fdb57f9 100644 --- a/simulatingrisk/hawkdove/model.py +++ b/simulatingrisk/hawkdove/model.py @@ -217,18 +217,6 @@ def step(self): + f"Final rolling average % hawk: {self.rolling_percent_hawk}" ) - @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 - ) - @property def max_agent_points(self): # what is the current largest point total of any agent? diff --git a/simulatingrisk/hawkdovevar/app.py b/simulatingrisk/hawkdovevar/app.py index ff9abfa..4480e78 100644 --- a/simulatingrisk/hawkdovevar/app.py +++ b/simulatingrisk/hawkdovevar/app.py @@ -10,7 +10,10 @@ ) from simulatingrisk.hawkdove.app import plot_hawks +# adjust single-risk params for variable risk jupyterviz_params_var = jupyterviz_params.copy() +# remove parameter for agent risk level; +# add parameters for adaptive risk strategies del jupyterviz_params_var["agent_risk_level"] jupyterviz_params_var.update( { From 2605f7c77516e8d4e616af23b4c319785c3195ce Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Thu, 19 Oct 2023 13:58:27 -0400 Subject: [PATCH 3/9] Add tests for variable hawk/dove; improve error handling --- simulatingrisk/hawkdovevar/model.py | 18 +++- tests/test_hawkdove.py | 11 -- tests/test_hawkdovevar.py | 150 ++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+), 13 deletions(-) create mode 100644 tests/test_hawkdovevar.py diff --git a/simulatingrisk/hawkdovevar/model.py b/simulatingrisk/hawkdovevar/model.py index 9c02b4f..31d481e 100644 --- a/simulatingrisk/hawkdovevar/model.py +++ b/simulatingrisk/hawkdovevar/model.py @@ -5,7 +5,9 @@ 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): @@ -25,6 +27,7 @@ def play(self): 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): @@ -65,6 +68,8 @@ class HawkDoveVariableRiskModel(HawkDoveModel): risk_attitudes = "variable" agent_class = HawkDoveVariableRiskAgent + supported_risk_adjustments = (None, "adopt", "average") + def __init__( self, grid_size, @@ -76,10 +81,19 @@ def __init__( super().__init__( grid_size, include_diagonals=include_diagonals, hawk_odds=hawk_odds ) - # 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 diff --git a/tests/test_hawkdove.py b/tests/test_hawkdove.py index 2b50d12..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(): @@ -90,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 06f835c5156b3aaad1d97b6d5abb505bcafb4def Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 24 Oct 2023 10:43:13 -0400 Subject: [PATCH 4/9] Add a label for round-adjustment parameter --- simulatingrisk/hawkdovevar/app.py | 1 + 1 file changed, 1 insertion(+) diff --git a/simulatingrisk/hawkdovevar/app.py b/simulatingrisk/hawkdovevar/app.py index 4480e78..ff9b5da 100644 --- a/simulatingrisk/hawkdovevar/app.py +++ b/simulatingrisk/hawkdovevar/app.py @@ -24,6 +24,7 @@ "description": "If and how agents update their risk level", }, "adjust_every": { + "label": "Adjustment frequency (# rounds)", "type": "SliderInt", "min": 1, "max": 30, From b0d0aa0a9e06349e783eb671dd747788cfcb9367 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 24 Oct 2023 10:43:36 -0400 Subject: [PATCH 5/9] Fix typo in app about placeholder content --- simulatingrisk/about_app.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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/). From abcc5b6ca090d899dcce6958c4ae31994e593ade Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 24 Oct 2023 11:01:53 -0400 Subject: [PATCH 6/9] Load about app content relative to app file --- simulatingrisk/app.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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())) From 14ddeb23da71cc1c6cf9321c0f51add4a072b145 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 24 Oct 2023 15:37:12 -0400 Subject: [PATCH 7/9] Remove model dependency in matplotlib histogram (prevents from updating) --- simulatingrisk/charts/histogram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simulatingrisk/charts/histogram.py b/simulatingrisk/charts/histogram.py index 46c9d6a..1932ef2 100644 --- a/simulatingrisk/charts/histogram.py +++ b/simulatingrisk/charts/histogram.py @@ -73,4 +73,4 @@ def plot_risk_histogram(model): 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) From 2687cbf1ebe7650e394cac77a31948ddd2d86d52 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Tue, 24 Oct 2023 15:52:58 -0400 Subject: [PATCH 8/9] Remove unused requirements text files/directories --- requirements.txt | 1 - requirements/main.txt | 0 2 files changed, 1 deletion(-) delete mode 100644 requirements.txt delete mode 100644 requirements/main.txt 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 From 48a13b6fd261b79e61b3b7260dfa23d49ff50bb2 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 25 Oct 2023 11:06:57 -0400 Subject: [PATCH 9/9] Clean up outdated comments and refactor common hawk/dove params --- simulatingrisk/charts/histogram.py | 6 +----- simulatingrisk/hawkdove/server.py | 21 ++++++++++++--------- simulatingrisk/hawkdovevar/app.py | 9 +++------ 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/simulatingrisk/charts/histogram.py b/simulatingrisk/charts/histogram.py index 1932ef2..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) 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 ff9b5da..346084d 100644 --- a/simulatingrisk/hawkdovevar/app.py +++ b/simulatingrisk/hawkdovevar/app.py @@ -5,16 +5,13 @@ 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 -# adjust single-risk params for variable risk -jupyterviz_params_var = jupyterviz_params.copy() -# remove parameter for agent risk level; -# add parameters for adaptive risk strategies -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": {