Skip to content
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: add option to adjust risk attitude based on recent or total payoff #54

Merged
merged 4 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions simulatingrisk/hawkdovemulti/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@
"values": neighborhood_sizes,
"label": "Adjustment neighborhood size",
},
"adjust_payoff": {
"type": "Select",
"label": "Adjustment comparison period",
"value": "recent",
"values": ["recent", "total"],
"description": "Compare recent payoff (since last adjustment "
+ "round) or total (cumulative from start) when adjusting risk attitudes",
},
}
)

Expand Down
50 changes: 48 additions & 2 deletions simulatingrisk/hawkdovemulti/model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import statistics
from collections import Counter
from enum import IntEnum
from functools import cached_property

from simulatingrisk.hawkdove.model import HawkDoveModel, HawkDoveAgent

Expand All @@ -12,6 +13,9 @@ class HawkDoveMultipleRiskAgent(HawkDoveAgent):
configuration.
"""

#: points since last adjustment round; starts at 0
recent_points = 0

def set_risk_level(self):
# risk level is based partially on neighborhood size,
# which is configurable at the model level
Expand All @@ -23,23 +27,49 @@ def set_risk_level(self):
)

def play(self):
# save total points before playing so we only need to calculate
# current round payoff once
prev_points = self.points
super().play()
# when enabled by the model, periodically adjust risk level

# add payoff from current round to recent points
self.recent_points += self.points - prev_points

if self.model.adjustment_round:
self.adjust_risk()
# reset to zero to track points until next adjustment round
self.recent_points = 0

@property
def adjust_neighbors(self):
"""neighbors to look at when adjusting risk attitude; uses
model adjust_neighborhood size"""
return self.get_neighbors(self.model.adjust_neighborhood)

@cached_property
def compare_payoff_field(self):
"""determine which payoff to compare depending on model option:
(cumulative/total or points since last adjustment round)"""
return "recent_points" if self.model.adjust_payoff == "recent" else "points"

@property
def compare_payoff(self):
"""payoff value to use for adjustment comparison
(depends on model configuration)"""
return getattr(self, self.compare_payoff_field)

@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.adjust_neighbors, key=lambda x: x.points, reverse=True)[0]

return sorted(
self.adjust_neighbors,
key=lambda x: getattr(x, self.compare_payoff_field),
reverse=True,
)[0]

def adjust_risk(self):
# look at neighbors
Expand All @@ -49,7 +79,10 @@ def adjust_risk(self):
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:
if (
best.compare_payoff > self.compare_payoff
and best.risk_level != self.risk_level
):
# adjust risk based on model configuration
if self.model.risk_adjustment == "adopt":
# adopt neighbor's risk level
Expand Down Expand Up @@ -113,19 +146,24 @@ class HawkDoveMultipleRiskModel(HawkDoveModel):
N rounds (default: 10)
:param adjust_neighborhood: size of neighborhood to look at when
adjusting risk attitudes; 4, 8, or 24 (default: play_neighborhood)
:param adjust_payoff: when comparing neighbors points for risk adjustment,
consider cumulative payoff (`total`) or payoff since the
last adjustment round (`recent`) (default: recent)
"""

risk_attitudes = "variable"
agent_class = HawkDoveMultipleRiskAgent

supported_risk_adjustments = (None, "adopt", "average")
supported_adjust_payoffs = ("recent", "total")

def __init__(
self,
grid_size,
risk_adjustment=None,
adjust_every=10,
adjust_neighborhood=None,
adjust_payoff="recent",
*args,
**kwargs,
):
Expand All @@ -142,11 +180,19 @@ def __init__(
f"Unsupported risk adjustment '{risk_adjustment}'; "
+ f"must be one of {risk_adjust_opts}"
)
if adjust_payoff not in self.supported_adjust_payoffs:
adjust_payoffs_opts = ", ".join(self.supported_adjust_payoffs)
raise ValueError(
f"Unsupported adjust payoff option '{adjust_payoff}'; "
+ f"must be one of {adjust_payoffs_opts}"
)
self.risk_adjustment = risk_adjustment
self.adjust_round_n = adjust_every
# if adjust neighborhood is not specified, then use the same size
# as play neighborhood
self.adjust_neighborhood = adjust_neighborhood or self.play_neighborhood
# store whether to compare cumulative payoff or since last adjustment round
self.adjust_payoff = adjust_payoff

@property
def adjustment_round(self) -> bool:
Expand Down
Loading