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

Follow on commit with minor changes #107

Closed
wants to merge 13 commits into from
Closed
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
9 changes: 4 additions & 5 deletions examples/sugarscape_cg/sugarscape_cg/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,9 @@ def __init__(self, width=50, height=50, initial_population=100):
import numpy as np

sugar_distribution = np.genfromtxt(Path(__file__).parent / "sugar-map.txt")
agent_id = 0
for _, (x, y) in self.grid.coord_iter():
max_sugar = sugar_distribution[x, y]
sugar = Sugar(agent_id, (x, y), self, max_sugar)
agent_id += 1
sugar = Sugar(self.next_id(), (x, y), self, max_sugar)
self.grid.place_agent(sugar, (x, y))
self.schedule.add(sugar)

Expand All @@ -62,8 +60,9 @@ def __init__(self, width=50, height=50, initial_population=100):
sugar = self.random.randrange(6, 25)
metabolism = self.random.randrange(2, 4)
vision = self.random.randrange(1, 6)
ssa = SsAgent(agent_id, (x, y), self, False, sugar, metabolism, vision)
agent_id += 1
ssa = SsAgent(
self.next_id(), (x, y), self, False, sugar, metabolism, vision
)
self.grid.place_agent(ssa, (x, y))
self.schedule.add(ssa)

Expand Down
43 changes: 43 additions & 0 deletions examples/sugarscape_scc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Sugarscape: Sex, Culture, and Conflict : The Emergence of History

## Summary

This is a work-in-progress implementation of Chapter 3 from *Growing Artificial Societies: Social Science from the Bottom Up.*, adding mechanics such as sexual reproduction, cultural exchange and combat.

This model greatly increases the complexity of interactions between agents, following on from Chapter 2's model.

## Mechanics Introduced

### Sexual Reproduction

We assign agents a trait, **Fertility**, according to which they can perform Agent Rule **S**:
- Select a neighbour at random
- If the neighbor is fertile and of the opposite sex and at least one of the agents has an empty neighboring site (for the baby), then a child is born
- Repeat for all neighbors

### Cultural Processes (WIP)

### Combat (WIP)

This model demonstrates visualisation of agent attributes.
## How to Run

To run the model interactively, run ``mesa runserver`` in this directory. e.g.

`$ mesa runserver`

Then open your browser to [http://127.0.0.1:8521/](http://127.0.0.1:8521/) and press **Reset**, then **Run**.

## Files

* `sugarscape_scc/agents.py`: Adds chapter-specific mechanics to `sugarscape_cg/agent.py`
* `sugarscape_scc/server.py`: Sets up the interactive server with charts visualising data.
* `sugarscape_scc/model.py`: Defines the Sugarscape model itself
* `run.py`: Launches a model visualization server.

## Further Reading

Epstein, J. M., & Axtell, R. (n.d.). *Growing Artificial Societies*: Social science from the bottom up. Brookings Institution Press, Chapter 3.


The ant sprite is taken from https://openclipart.org/detail/229519/ant-silhouette, with CC0 1.0 license.
3 changes: 3 additions & 0 deletions examples/sugarscape_scc/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from sugarscape_scc.server import server

server.launch(open_browser=True)
Empty file.
169 changes: 169 additions & 0 deletions examples/sugarscape_scc/sugarscape_scc/agents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import math

import mesa


def get_distance(pos_1, pos_2):
"""Get the distance between two point

Args:
pos_1, pos_2: Coordinate tuples for both points.
"""
x1, y1 = pos_1
x2, y2 = pos_2
dx = x1 - x2
dy = y1 - y2
return math.sqrt(dx**2 + dy**2)


class SsAgent(mesa.Agent):
def __init__(
self,
unique_id,
pos,
model,
moore=False,
sugar=0,
metabolism=0,
vision=0,
fertile=40,
):
# first generation agents are randomly assigned metabolism and vision,
# children get a contribution from their parents

super().__init__(unique_id, model)
self.pos = pos
self.moore = moore
self.sugar = sugar
self.metabolism = metabolism
self.vision = vision
self.age = 0
self.fertile = fertile
self.age_of_death = self.random.randrange(60, 100)
self.gender = self.random.randint(0, 1) # 0 is FEMALE, 1 is MALE
self.children = [] # maybe stores the IDs of the kids of the agent?
self.age_of_death = self.random.randrange(60, 100)
self.gender = self.random.randint(0, 1) # 0 is FEMALE, 1 is MALE

def is_occupied(self, pos):
this_cell = self.model.grid.get_cell_list_contents([pos])
return any(isinstance(agent, SsAgent) for agent in this_cell)

def is_fertile(self) -> bool:
# reduced some of the randomness in determining when agents can no longer reproduce
if self.age < 15:
return False

if (self.gender == 1) and (self.age > 60):
return False

if (self.gender == 0) and (self.age > 50):
return False
return self.sugar < self.fertile

def get_sugar(self, pos):
this_cell = self.model.grid.get_cell_list_contents([pos])
for agent in this_cell:
if type(agent) is Sugar:
return agent

def move(self):
# Get neighborhood within vision
neighbors = [
i
for i in self.model.grid.get_neighborhood(
self.pos, self.moore, False, radius=self.vision
)
if not self.is_occupied(i)
]
neighbors.append(self.pos)
# Look for location with the most sugar
max_sugar = max(self.get_sugar(pos).amount for pos in neighbors)
candidates = [
pos for pos in neighbors if self.get_sugar(pos).amount == max_sugar
]
# Narrow down to the nearest ones
min_dist = min(get_distance(self.pos, pos) for pos in candidates)
final_candidates = [
pos for pos in candidates if get_distance(self.pos, pos) == min_dist
]
self.random.shuffle(final_candidates)
self.model.grid.move_agent(self, final_candidates[0])

def eat(self):
sugar_patch = self.get_sugar(self.pos)
self.sugar = self.sugar - self.metabolism + sugar_patch.amount
sugar_patch.amount = 0

def sex(self):
potential_mates = [
i
for i in self.model.grid.get_neighbors(
self.pos, self.moore, include_center=False, radius=self.vision
)
if (
(type(i) is SsAgent)
and (i.is_fertile() is True)
and (i.gender != self.gender)
)
]

# check for empty spots next to self
empty_cells = [
i
for i in self.model.grid.get_neighborhood(
self.pos, self.moore, include_center=False, radius=1
)
if not self.is_occupied(i)
]
self.random.shuffle(empty_cells)
for neighbor in potential_mates:
if self.sugar < self.fertile:
break

if len(empty_cells) == 0:
continue # placeholder
# check for first empty spot next to neighbor
# if none found iterate to next neighbor
endowment = (self.sugar / 2) + (neighbor.sugar / 2)
self.sugar -= self.sugar / 2
neighbor.sugar -= neighbor.sugar / 2
ssa = SsAgent(
self.model.next_id(),
empty_cells[0],
self.model,
False,
sugar=endowment,
metabolism=self.random.choice([neighbor.metabolism, self.metabolism]),
vision=self.random.choice([neighbor.vision, self.vision]),
)
self.model.grid.place_agent(ssa, empty_cells[0])
self.model.schedule.add(ssa)

# iterate through list

def inheritance():
pass

def step(self):
self.move()
self.eat()
if self.is_fertile() is True:
self.sex() # reproduction condition

if (self.sugar <= 0) or (self.age == self.age_of_death):
self.model.grid.remove_agent(self)
self.model.schedule.remove(self) # death conditions
self.inheritance()
self.age += 1


class Sugar(mesa.Agent):
def __init__(self, unique_id, pos, model, max_sugar, growback_rule=1):
super().__init__(unique_id, model)
self.amount = max_sugar
self.max_sugar = max_sugar
self.growback = growback_rule

def step(self):
self.amount = min([self.max_sugar, self.amount + self.growback])
95 changes: 95 additions & 0 deletions examples/sugarscape_scc/sugarscape_scc/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""
Sugarscape Constant Growback Model
================================

Replication of the model found in Netlogo:
Li, J. and Wilensky, U. (2009). NetLogo Sugarscape 2 Constant Growback model.
http://ccl.northwestern.edu/netlogo/models/Sugarscape2ConstantGrowback.
Center for Connected Learning and Computer-Based Modeling,
Northwestern University, Evanston, IL.
"""

from pathlib import Path

import mesa

from .agents import SsAgent, Sugar


class SugarscapeScc(mesa.Model):
"""
Sugarscape 3 SCC
"""

verbose = True # Print-monitoring

def __init__(self, width=50, height=50, initial_population=100):
"""
Create a new Constant Growback model with the given parameters.

Args:
initial_population: Number of population to start with
"""
super().__init__()

# Set parameters
self.width = width
self.height = height
self.initial_population = initial_population
self.schedule = mesa.time.RandomActivationByType(self)
self.grid = mesa.space.MultiGrid(self.width, self.height, torus=False)
self.datacollector = mesa.DataCollector(
model_reporters={"SsAgent": lambda m: m.schedule.get_type_count(SsAgent)},
agent_reporters={
"age": lambda a: a.age if (isinstance(a, SsAgent)) else None
},
)
# Create sugar
import numpy as np

sugar_distribution = np.genfromtxt(Path(__file__).parent / "sugar-map.txt")
for _, (x, y) in self.grid.coord_iter():
max_sugar = sugar_distribution[x, y]
sugar = Sugar(self.next_id(), (x, y), self, max_sugar)
self.grid.place_agent(sugar, (x, y))
self.schedule.add(sugar)

# Create agent:
for _ in range(self.initial_population):
x = self.random.randrange(self.width)
y = self.random.randrange(self.height)
sugar = self.random.randrange(50, 100)
metabolism = self.random.randrange(2, 4)
vision = self.random.randrange(1, 6)
ssa = SsAgent(
self.next_id(), (x, y), self, False, sugar, metabolism, vision
)
self.grid.place_agent(ssa, (x, y))
self.schedule.add(ssa)

self.running = True
self.datacollector.collect(self)

def step(self):
self.schedule.step()
# collect data
self.datacollector.collect(self)
if self.verbose:
print([self.schedule.time, self.schedule.get_type_count(SsAgent)])

def run_model(self, step_count=200):
if self.verbose:
print(
"Initial number Sugarscape Agent: ",
self.schedule.get_type_count(SsAgent),
)

for i in range(step_count):
self.step()

if self.verbose:
print("")
print(
"Final number Sugarscape Agent: ",
self.schedule.get_type_count(SsAgent),
)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 40 additions & 0 deletions examples/sugarscape_scc/sugarscape_scc/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import mesa

# TODO implement age graph as well
from .agents import SsAgent, Sugar
from .model import SugarscapeScc

color_dic = {4: "#005C00", 3: "#008300", 2: "#00AA00", 1: "#00F800"}


def SsAgent_portrayal(agent):
if agent is None:
return

if type(agent) is SsAgent:
return {"Shape": "sugarscape_scc/resources/ant.png", "scale": 0.9, "Layer": 1}

elif type(agent) is Sugar:
color = color_dic[agent.amount] if agent.amount != 0 else "#D6F5D6"
return {
"Color": color,
"Shape": "rect",
"Filled": "true",
"Layer": 0,
"w": 1,
"h": 1,
}

return {}


canvas_element = mesa.visualization.CanvasGrid(SsAgent_portrayal, 50, 50, 500, 500)
chart_element = mesa.visualization.ChartModule(
[{"Label": "SsAgent", "Color": "#AA0000"}], data_collector_name="datacollector"
)
bar_graph = mesa.visualization.BarChartModule(
[{"Label": "age", "Color": "#AAAAAA"}], data_collector_name="datacollector"
)
server = mesa.visualization.ModularServer(
SugarscapeScc, [canvas_element, chart_element, bar_graph], "Sugarscape 3 SCC"
)
Loading