Skip to content
Draft
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
20 changes: 20 additions & 0 deletions examples/UltimatumModel/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import matplotlib.pyplot as plt
from ultimatum_game.model import UltimatumModel

# Setup and run
model = UltimatumModel(n=1000)
for i in range(1000):
model.step()

# Retrieve data
data = model.datacollector.get_model_vars_dataframe()

# Visualization
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
data["Accept Rate"].plot(ax=axes[0, 0], title="Accept Rate %")
data["Avg Offer"].plot(ax=axes[0, 1], title="Average Offer")
data["Proposer Avg"].plot(ax=axes[1, 0], title="Proposer Earnings")
data["Responder Avg"].plot(ax=axes[1, 1], title="Responder Earnings")

plt.tight_layout()
plt.show()
37 changes: 37 additions & 0 deletions examples/UltimatumModel/ultimatum_game/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Iterated Ultimatum Game with Several Agents

## Summary

The Ultimatum Game is an experimental economics game in which two players interact to decide how to divide a sum of money. This model consists of agents that pair up, one randomly taking the role of proposer and the other the role of responder.

The total stake in this model is 100 units. The proposer chooses a positive integer between 0 and 99 as the offer to pay the responder. The responder has two options:

1. Accept: The offer is recognized as fair, and the money is divided according to the offer.

2. Reject: The offer is recognized as unfair; it is rejected, and neither party receives any amount.

Behavioral studies on this game have shown that, contrary to classic economic theory, responders do not always accept every offer greater than zero.

In this model, initial offers and thresholds start randomly. However, after each step, agents learn based on whether the interaction was successful. Each offer is recorded in the players' memory, increasing the probability of choosing that strategy in the future. The higher the benefit of a specific offer or threshold, the higher its probability weight. In each new step, agents are randomly re-paired and roles are reassigned.

## How to Run

To run the model and see the results, ensure you have mesa, numpy, and matplotlib installed, then execute:

```
$ python run.py
```

## Files

* ``agents.py``: Contains the ``UltimatumAgent`` class and the learning logic.

* ``model.py``: Contains the ``UltimatumModel`` class. The model takes a positive integer ``n`` as an argument to determine the number of agents.

* ``run.py``: The script used to run the simulation for a specified number of steps and visualize the data.

## Further Reading

This model is adapted from:

* Camerer, Colin F. 2003. Behavioral Game Theory: Experiments in Strategic Interaction. Princeton, NJ: Princeton University Press.
Empty file.
12 changes: 12 additions & 0 deletions examples/UltimatumModel/ultimatum_game/agents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import mesa
import numpy as np


class UltimatumAgent(mesa.Agent):
def __init__(self, model, unique_id):
super().__init__(model)
self.wealth = 0.0
self.offer_memory = np.zeros(len(model.offers))
self.offer_chosen = 0
self.thresh_memory = np.zeros(len(model.thresholds))
self.thresh_chosen = 0
116 changes: 116 additions & 0 deletions examples/UltimatumModel/ultimatum_game/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import mesa
import numpy as np

from ultimatum_game.agents import UltimatumAgent


class UltimatumModel(mesa.Model):
def __init__(self, n, stake=100):
super().__init__()
self.num_agents = n
self.stake = stake
self.grid = mesa.space.MultiGrid(10, 10, torus=True)
self.offers = list(range(self.stake))
self.thresholds = list(range(self.stake))
self.learning_rate = 0.2
self.decay_rate = 0.95
# Data collection
self.datacollector = mesa.DataCollector(
model_reporters={
"Accept Rate": "current_accept_rate",
"Avg Offer": "current_avg_offer",
"Proposer Avg": "current_proposer_avg",
"Responder Avg": "current_responder_avg",
}
)
self.current_accept_rate = 0
self.current_avg_offer = 0
self.current_proposer_avg = 0
self.current_responder_avg = 0

# Create Agents
for i in range(self.num_agents):
agent = UltimatumAgent(self, i)
# Random position
x = self.random.randrange(self.grid.width)
y = self.random.randrange(self.grid.height)
self.grid.place_agent(agent, (x, y))

def step(self):
self.agents.shuffle_do("step")
agents = [a for a in self.agents]
np.random.shuffle(agents)
pairs = [(agents[i], agents[i + 1]) for i in range(0, len(agents), 2)]

# Stats trackers
offers_made = []
accept_count = 0
total_proposer_pay = 0
total_responder_pay = 0

# Game Settings
for first_agent, second_agent in pairs:
if np.random.random() < 0.5:
proposer, responder = first_agent, second_agent
else:
proposer, responder = second_agent, first_agent

proposer_offer = (
proposer.offer_chosen
if np.sum(proposer.offer_memory) > 0
else np.random.randint(0, len(proposer.offer_memory))
)
if np.sum(proposer.offer_memory) > 0:
exp_mem = np.exp(proposer.offer_memory / 5.0)
probs = exp_mem / exp_mem.sum()
proposer_offer = np.random.choice(len(proposer.offer_memory), p=probs)
offer = self.offers[proposer_offer]
proposer.offer_chosen = proposer_offer

responder_thresh = (
responder.thresh_chosen
if np.sum(responder.thresh_memory) > 0
else np.random.randint(0, len(responder.thresh_memory))
)
if np.sum(responder.thresh_memory) > 0:
exp_mem = np.exp(responder.thresh_memory / 5.0)
probs = exp_mem / exp_mem.sum()
responder_thresh = np.random.choice(
len(responder.thresh_memory), p=probs
)
threshold = self.thresholds[responder_thresh]
responder.thresh_chosen = responder_thresh

# Play the game
if offer >= threshold:
p_pay = self.stake - offer
r_pay = offer
accept_count += 1
else:
p_pay = 0
r_pay = 0

# Learn
proposer.offer_memory[proposer_offer] += self.learning_rate * p_pay
responder.thresh_memory[responder_thresh] += self.learning_rate * r_pay

# Decay memories
proposer.offer_memory *= self.decay_rate
responder.thresh_memory *= self.decay_rate

# Update wealth
proposer.wealth += p_pay
responder.wealth += r_pay

# Stats
offers_made.append(offer)
total_proposer_pay += p_pay
total_responder_pay += r_pay

# Model stats
self.current_accept_rate = (accept_count / len(pairs)) * 100
self.current_avg_offer = np.mean(offers_made)
self.current_proposer_avg = total_proposer_pay / len(pairs)
self.current_responder_avg = total_responder_pay / len(pairs)

self.datacollector.collect(self)