-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Hawk/dove updating risk attitudes #36
Changes from all commits
0d5980c
e49b674
2605f7c
06f835c
b0d0aa0
abcc5b6
14ddeb2
2687cbf
48a13b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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/). |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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]) | ||
) | ||
Comment on lines
+33
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Very, very cool, and slick. I remember talking about this and/or similar strategies of risk adjustment with David and Lara and you. |
||
|
||
|
||
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 | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"<HawkDoveSingleRiskAgent id={agent_id} r={risk_level}>" | ||
assert ( | ||
repr(agent) | ||
== f"<HawkDoveSingleRiskAgent id={agent_id} r={risk_level} points=0>" | ||
) | ||
Comment on lines
+69
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice test |
||
|
||
|
||
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)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self.random
presumably comes from the base mesa class? Otherwise wondering what it refers toThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, mesa provides access to built-in random methods that works just like the main python library random module
It's in mentioned in their documentation best practices, it would allow using a random seed at model instantiation: https://mesa.readthedocs.io/en/stable/best-practices.html#randomization