-
Notifications
You must be signed in to change notification settings - Fork 840
/
time.py
173 lines (132 loc) · 5.42 KB
/
time.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# -*- coding: utf-8 -*-
"""
Mesa Time Module
================
Objects for handling the time component of a model. In particular, this module
contains Schedulers, which handle agent activation. A Scheduler is an object
which controls when agents are called upon to act, and when.
The activation order can have a serious impact on model behavior, so it's
important to specify it explicitly. Example simple activation regimes include
activating all agents in the same order every step, shuffling the activation
order every time, activating each agent *on average* once per step, and more.
Key concepts:
Step: Many models advance in 'steps'. A step may involve the activation of
all agents, or a random (or selected) subset of them. Each agent in turn
may have their own step() method.
Time: Some models may simulate a continuous 'clock' instead of discrete
steps. However, by default, the Time is equal to the number of steps the
model has taken.
TODO: Have the schedulers use the model's randomizer, to keep random number
seeds consistent and allow for replication.
"""
import random
class BaseScheduler:
""" Simplest scheduler; activates agents one at a time, in the order
they were added.
Assumes that each agent added has a *step* method which takes no arguments.
(This is explicitly meant to replicate the scheduler in MASON).
"""
model = None
steps = 0
time = 0
agents = []
def __init__(self, model):
""" Create a new, empty BaseScheduler. """
self.model = model
self.steps = 0
self.time = 0
self.agents = []
def add(self, agent):
""" Add an Agent object to the schedule.
Args:
agent: An Agent to be added to the schedule. NOTE: The agent must
have a step() method.
"""
self.agents.append(agent)
def remove(self, agent):
""" Remove all instances of a given agent from the schedule.
Args:
agent: An agent object.
"""
while agent in self.agents:
self.agents.remove(agent)
def step(self):
""" Execute the step of all the agents, one at a time. """
for agent in self.agents[:]:
agent.step()
self.steps += 1
self.time += 1
def get_agent_count(self):
""" Returns the current number of agents in the queue. """
return len(self.agents)
class RandomActivation(BaseScheduler):
""" A scheduler which activates each agent once per step, in random order,
with the order reshuffled every step.
This is equivalent to the NetLogo 'ask agents...' and is generally the
default behavior for an ABM.
Assumes that all agents have a step(model) method.
"""
def step(self):
""" Executes the step of all agents, one at a time, in
random order.
"""
random.shuffle(self.agents)
for agent in self.agents[:]:
agent.step()
self.steps += 1
self.time += 1
class SimultaneousActivation(BaseScheduler):
""" A scheduler to simulate the simultaneous activation of all the agents.
This scheduler requires that each agent have two methods: step and advance.
step() activates the agent and stages any necessary changes, but does not
apply them yet. advance() then applies the changes.
"""
def step(self):
""" Step all agents, then advance them. """
for agent in self.agents[:]:
agent.step()
for agent in self.agents[:]:
agent.advance()
self.steps += 1
self.time += 1
class StagedActivation(BaseScheduler):
""" A scheduler which allows agent activation to be divided into several
stages instead of a single `step` method. All agents execute one stage
before moving on to the next.
Agents must have all the stage methods implemented. Stage methods take a
model object as their only argument.
This schedule tracks steps and time separately. Time advances in fractional
increments of 1 / (# of stages), meaning that 1 step = 1 unit of time.
"""
stage_list = []
shuffle = False
shuffle_between_stages = False
stage_time = 1
def __init__(self, model, stage_list=None, shuffle=False,
shuffle_between_stages=False):
""" Create an empty Staged Activation schedule.
Args:
model: Model object associated with the schedule.
stage_list: List of strings of names of stages to run, in the
order to run them in.
shuffle: If True, shuffle the order of agents each step.
shuffle_between_stages: If True, shuffle the agents after each
stage; otherwise, only shuffle at the start
of each step.
"""
super().__init__(model)
self.stage_list = stage_list or ["step"]
self.shuffle = shuffle
self.shuffle_between_stages = shuffle_between_stages
self.stage_time = 1 / len(self.stage_list)
def step(self):
""" Executes all the stages for all agents. """
if self.shuffle:
random.shuffle(self.agents)
for stage in self.stage_list:
for agent in self.agents[:]:
getattr(agent, stage)() # Run stage
if self.shuffle_between_stages:
random.shuffle(self.agents)
self.time += self.stage_time
self.steps += 1