Mesa 2.3.0.dev0__py3-none-any.whl → 2.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of Mesa might be problematic. Click here for more details.
- mesa/__init__.py +2 -1
- mesa/agent.py +2 -16
- mesa/datacollection.py +12 -6
- mesa/experimental/__init__.py +4 -0
- mesa/experimental/cell_space/__init__.py +23 -0
- mesa/experimental/cell_space/cell.py +152 -0
- mesa/experimental/cell_space/cell_agent.py +37 -0
- mesa/experimental/cell_space/cell_collection.py +81 -0
- mesa/experimental/cell_space/discrete_space.py +64 -0
- mesa/experimental/cell_space/grid.py +204 -0
- mesa/experimental/cell_space/network.py +40 -0
- mesa/experimental/components/altair.py +13 -1
- mesa/experimental/components/matplotlib.py +3 -0
- mesa/experimental/devs/__init__.py +4 -0
- mesa/experimental/devs/eventlist.py +166 -0
- mesa/experimental/devs/examples/epstein_civil_violence.py +273 -0
- mesa/experimental/devs/examples/wolf_sheep.py +250 -0
- mesa/experimental/devs/simulator.py +293 -0
- mesa/experimental/jupyter_viz.py +51 -31
- mesa/model.py +2 -4
- mesa/space.py +27 -13
- mesa/time.py +3 -110
- {mesa-2.3.0.dev0.dist-info → mesa-2.3.1.dist-info}/METADATA +4 -3
- mesa-2.3.1.dist-info/RECORD +45 -0
- {mesa-2.3.0.dev0.dist-info → mesa-2.3.1.dist-info}/WHEEL +1 -1
- mesa-2.3.0.dev0.dist-info/RECORD +0 -33
- {mesa-2.3.0.dev0.dist-info → mesa-2.3.1.dist-info}/entry_points.txt +0 -0
- {mesa-2.3.0.dev0.dist-info → mesa-2.3.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Wolf-Sheep Predation Model
|
|
3
|
+
================================
|
|
4
|
+
|
|
5
|
+
Replication of the model found in NetLogo:
|
|
6
|
+
Wilensky, U. (1997). NetLogo Wolf Sheep Predation model.
|
|
7
|
+
http://ccl.northwestern.edu/netlogo/models/WolfSheepPredation.
|
|
8
|
+
Center for Connected Learning and Computer-Based Modeling,
|
|
9
|
+
Northwestern University, Evanston, IL.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import mesa
|
|
13
|
+
from mesa.experimental.devs.simulator import ABMSimulator
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Animal(mesa.Agent):
|
|
17
|
+
def __init__(self, unique_id, model, moore, energy, p_reproduce, energy_from_food):
|
|
18
|
+
super().__init__(unique_id, model)
|
|
19
|
+
self.energy = energy
|
|
20
|
+
self.p_reproduce = p_reproduce
|
|
21
|
+
self.energy_from_food = energy_from_food
|
|
22
|
+
self.moore = moore
|
|
23
|
+
|
|
24
|
+
def random_move(self):
|
|
25
|
+
next_moves = self.model.grid.get_neighborhood(self.pos, self.moore, True)
|
|
26
|
+
next_move = self.random.choice(next_moves)
|
|
27
|
+
# Now move:
|
|
28
|
+
self.model.grid.move_agent(self, next_move)
|
|
29
|
+
|
|
30
|
+
def spawn_offspring(self):
|
|
31
|
+
self.energy /= 2
|
|
32
|
+
offspring = self.__class__(
|
|
33
|
+
self.model.next_id(),
|
|
34
|
+
self.model,
|
|
35
|
+
self.moore,
|
|
36
|
+
self.energy,
|
|
37
|
+
self.p_reproduce,
|
|
38
|
+
self.energy_from_food,
|
|
39
|
+
)
|
|
40
|
+
self.model.grid.place_agent(offspring, self.pos)
|
|
41
|
+
|
|
42
|
+
def feed(self): ...
|
|
43
|
+
|
|
44
|
+
def die(self):
|
|
45
|
+
self.model.grid.remove_agent(self)
|
|
46
|
+
self.remove()
|
|
47
|
+
|
|
48
|
+
def step(self):
|
|
49
|
+
self.random_move()
|
|
50
|
+
self.energy -= 1
|
|
51
|
+
|
|
52
|
+
self.feed()
|
|
53
|
+
|
|
54
|
+
if self.energy < 0:
|
|
55
|
+
self.die()
|
|
56
|
+
elif self.random.random() < self.p_reproduce:
|
|
57
|
+
self.spawn_offspring()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class Sheep(Animal):
|
|
61
|
+
"""
|
|
62
|
+
A sheep that walks around, reproduces (asexually) and gets eaten.
|
|
63
|
+
|
|
64
|
+
The init is the same as the RandomWalker.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def feed(self):
|
|
68
|
+
# If there is grass available, eat it
|
|
69
|
+
agents = self.model.grid.get_cell_list_contents(self.pos)
|
|
70
|
+
grass_patch = next(obj for obj in agents if isinstance(obj, GrassPatch))
|
|
71
|
+
if grass_patch.fully_grown:
|
|
72
|
+
self.energy += self.energy_from_food
|
|
73
|
+
grass_patch.fully_grown = False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class Wolf(Animal):
|
|
77
|
+
"""
|
|
78
|
+
A wolf that walks around, reproduces (asexually) and eats sheep.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
def feed(self):
|
|
82
|
+
agents = self.model.grid.get_cell_list_contents(self.pos)
|
|
83
|
+
sheep = [obj for obj in agents if isinstance(obj, Sheep)]
|
|
84
|
+
if len(sheep) > 0:
|
|
85
|
+
sheep_to_eat = self.random.choice(sheep)
|
|
86
|
+
self.energy += self.energy
|
|
87
|
+
|
|
88
|
+
# Kill the sheep
|
|
89
|
+
sheep_to_eat.die()
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class GrassPatch(mesa.Agent):
|
|
93
|
+
"""
|
|
94
|
+
A patch of grass that grows at a fixed rate and it is eaten by sheep
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def fully_grown(self) -> bool:
|
|
99
|
+
return self._fully_grown
|
|
100
|
+
|
|
101
|
+
@fully_grown.setter
|
|
102
|
+
def fully_grown(self, value: bool):
|
|
103
|
+
self._fully_grown = value
|
|
104
|
+
|
|
105
|
+
if not value:
|
|
106
|
+
self.model.simulator.schedule_event_relative(
|
|
107
|
+
setattr,
|
|
108
|
+
self.grass_regrowth_time,
|
|
109
|
+
function_args=[self, "fully_grown", True],
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def __init__(self, unique_id, model, fully_grown, countdown, grass_regrowth_time):
|
|
113
|
+
"""
|
|
114
|
+
Creates a new patch of grass
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
grown: (boolean) Whether the patch of grass is fully grown or not
|
|
118
|
+
countdown: Time for the patch of grass to be fully grown again
|
|
119
|
+
"""
|
|
120
|
+
super().__init__(unique_id, model)
|
|
121
|
+
self._fully_grown = fully_grown
|
|
122
|
+
self.grass_regrowth_time = grass_regrowth_time
|
|
123
|
+
|
|
124
|
+
if not self.fully_grown:
|
|
125
|
+
self.model.simulator.schedule_event_relative(
|
|
126
|
+
setattr, countdown, function_args=[self, "fully_grown", True]
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def set_fully_grown(self):
|
|
130
|
+
self.fully_grown = True
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class WolfSheep(mesa.Model):
|
|
134
|
+
"""
|
|
135
|
+
Wolf-Sheep Predation Model
|
|
136
|
+
|
|
137
|
+
A model for simulating wolf and sheep (predator-prey) ecosystem modelling.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
def __init__(
|
|
141
|
+
self,
|
|
142
|
+
height,
|
|
143
|
+
width,
|
|
144
|
+
initial_sheep,
|
|
145
|
+
initial_wolves,
|
|
146
|
+
sheep_reproduce,
|
|
147
|
+
wolf_reproduce,
|
|
148
|
+
grass_regrowth_time,
|
|
149
|
+
wolf_gain_from_food=13,
|
|
150
|
+
sheep_gain_from_food=5,
|
|
151
|
+
moore=False,
|
|
152
|
+
simulator=None,
|
|
153
|
+
seed=None,
|
|
154
|
+
):
|
|
155
|
+
"""
|
|
156
|
+
Create a new Wolf-Sheep model with the given parameters.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
initial_sheep: Number of sheep to start with
|
|
160
|
+
initial_wolves: Number of wolves to start with
|
|
161
|
+
sheep_reproduce: Probability of each sheep reproducing each step
|
|
162
|
+
wolf_reproduce: Probability of each wolf reproducing each step
|
|
163
|
+
wolf_gain_from_food: Energy a wolf gains from eating a sheep
|
|
164
|
+
grass: Whether to have the sheep eat grass for energy
|
|
165
|
+
grass_regrowth_time: How long it takes for a grass patch to regrow
|
|
166
|
+
once it is eaten
|
|
167
|
+
sheep_gain_from_food: Energy sheep gain from grass, if enabled.
|
|
168
|
+
moore:
|
|
169
|
+
"""
|
|
170
|
+
super().__init__(seed=seed)
|
|
171
|
+
# Set parameters
|
|
172
|
+
self.height = height
|
|
173
|
+
self.width = width
|
|
174
|
+
self.initial_sheep = initial_sheep
|
|
175
|
+
self.initial_wolves = initial_wolves
|
|
176
|
+
self.simulator = simulator
|
|
177
|
+
|
|
178
|
+
# self.sheep_reproduce = sheep_reproduce
|
|
179
|
+
# self.wolf_reproduce = wolf_reproduce
|
|
180
|
+
# self.grass_regrowth_time = grass_regrowth_time
|
|
181
|
+
# self.wolf_gain_from_food = wolf_gain_from_food
|
|
182
|
+
# self.sheep_gain_from_food = sheep_gain_from_food
|
|
183
|
+
# self.moore = moore
|
|
184
|
+
|
|
185
|
+
self.grid = mesa.space.MultiGrid(self.height, self.width, torus=False)
|
|
186
|
+
|
|
187
|
+
for _ in range(self.initial_sheep):
|
|
188
|
+
pos = (
|
|
189
|
+
self.random.randrange(self.width),
|
|
190
|
+
self.random.randrange(self.height),
|
|
191
|
+
)
|
|
192
|
+
energy = self.random.randrange(2 * sheep_gain_from_food)
|
|
193
|
+
sheep = Sheep(
|
|
194
|
+
self.next_id(),
|
|
195
|
+
self,
|
|
196
|
+
moore,
|
|
197
|
+
energy,
|
|
198
|
+
sheep_reproduce,
|
|
199
|
+
sheep_gain_from_food,
|
|
200
|
+
)
|
|
201
|
+
self.grid.place_agent(sheep, pos)
|
|
202
|
+
|
|
203
|
+
# Create wolves
|
|
204
|
+
for _ in range(self.initial_wolves):
|
|
205
|
+
pos = (
|
|
206
|
+
self.random.randrange(self.width),
|
|
207
|
+
self.random.randrange(self.height),
|
|
208
|
+
)
|
|
209
|
+
energy = self.random.randrange(2 * wolf_gain_from_food)
|
|
210
|
+
wolf = Wolf(
|
|
211
|
+
self.next_id(),
|
|
212
|
+
self,
|
|
213
|
+
moore,
|
|
214
|
+
energy,
|
|
215
|
+
wolf_reproduce,
|
|
216
|
+
wolf_gain_from_food,
|
|
217
|
+
)
|
|
218
|
+
self.grid.place_agent(wolf, pos)
|
|
219
|
+
|
|
220
|
+
# Create grass patches
|
|
221
|
+
possibly_fully_grown = [True, False]
|
|
222
|
+
for _agent, pos in self.grid.coord_iter():
|
|
223
|
+
fully_grown = self.random.choice(possibly_fully_grown)
|
|
224
|
+
if fully_grown:
|
|
225
|
+
countdown = grass_regrowth_time
|
|
226
|
+
else:
|
|
227
|
+
countdown = self.random.randrange(grass_regrowth_time)
|
|
228
|
+
patch = GrassPatch(
|
|
229
|
+
self.next_id(), self, fully_grown, countdown, grass_regrowth_time
|
|
230
|
+
)
|
|
231
|
+
self.grid.place_agent(patch, pos)
|
|
232
|
+
|
|
233
|
+
def step(self):
|
|
234
|
+
self.get_agents_of_type(Sheep).shuffle(inplace=True).do("step")
|
|
235
|
+
self.get_agents_of_type(Wolf).shuffle(inplace=True).do("step")
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
if __name__ == "__main__":
|
|
239
|
+
import time
|
|
240
|
+
|
|
241
|
+
simulator = ABMSimulator()
|
|
242
|
+
|
|
243
|
+
model = WolfSheep(25, 25, 60, 40, 0.2, 0.1, 20, simulator=simulator, seed=15)
|
|
244
|
+
|
|
245
|
+
simulator.setup(model)
|
|
246
|
+
|
|
247
|
+
start_time = time.perf_counter()
|
|
248
|
+
simulator.run(100)
|
|
249
|
+
print(simulator.time)
|
|
250
|
+
print("Time:", time.perf_counter() - start_time)
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numbers
|
|
4
|
+
from typing import Any, Callable
|
|
5
|
+
|
|
6
|
+
from mesa import Model
|
|
7
|
+
|
|
8
|
+
from .eventlist import EventList, Priority, SimulationEvent
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Simulator:
|
|
12
|
+
"""The Simulator controls the time advancement of the model.
|
|
13
|
+
|
|
14
|
+
The simulator uses next event time progression to advance the simulation time, and execute the next event
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
event_list (EventList): The list of events to execute
|
|
18
|
+
time (float | int): The current simulation time
|
|
19
|
+
time_unit (type) : The unit of the simulation time
|
|
20
|
+
model (Model): The model to simulate
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
# TODO: add replication support
|
|
26
|
+
# TODO: add experimentation support
|
|
27
|
+
|
|
28
|
+
def __init__(self, time_unit: type, start_time: int | float):
|
|
29
|
+
# should model run in a separate thread,
|
|
30
|
+
# and we can then interact with start, stop, run_until, and step?
|
|
31
|
+
self.event_list = EventList()
|
|
32
|
+
self.start_time = start_time
|
|
33
|
+
self.time_unit = time_unit
|
|
34
|
+
|
|
35
|
+
self.time = self.start_time
|
|
36
|
+
self.model = None
|
|
37
|
+
|
|
38
|
+
def check_time_unit(self, time: int | float) -> bool: ...
|
|
39
|
+
|
|
40
|
+
def setup(self, model: Model) -> None:
|
|
41
|
+
"""Set up the simulator with the model to simulate
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
model (Model): The model to simulate
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
self.event_list.clear()
|
|
48
|
+
self.model = model
|
|
49
|
+
|
|
50
|
+
def reset(self):
|
|
51
|
+
"""Reset the simulator by clearing the event list and removing the model to simulate"""
|
|
52
|
+
self.event_list.clear()
|
|
53
|
+
self.model = None
|
|
54
|
+
self.time = self.start_time
|
|
55
|
+
|
|
56
|
+
def run_until(self, end_time: int | float) -> None:
|
|
57
|
+
while True:
|
|
58
|
+
try:
|
|
59
|
+
event = self.event_list.pop_event()
|
|
60
|
+
except IndexError: # event list is empty
|
|
61
|
+
self.time = end_time
|
|
62
|
+
break
|
|
63
|
+
|
|
64
|
+
if event.time <= end_time:
|
|
65
|
+
self.time = event.time
|
|
66
|
+
event.execute()
|
|
67
|
+
else:
|
|
68
|
+
self.time = end_time
|
|
69
|
+
self._schedule_event(event) # reschedule event
|
|
70
|
+
break
|
|
71
|
+
|
|
72
|
+
def run_for(self, time_delta: int | float):
|
|
73
|
+
"""run the simulator for the specified time delta
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
time_delta (float| int): The time delta. The simulator is run from the current time to the current time
|
|
77
|
+
plus the time delta
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
end_time = self.time + time_delta
|
|
81
|
+
self.run_until(end_time)
|
|
82
|
+
|
|
83
|
+
def schedule_event_now(
|
|
84
|
+
self,
|
|
85
|
+
function: Callable,
|
|
86
|
+
priority: Priority = Priority.DEFAULT,
|
|
87
|
+
function_args: list[Any] | None = None,
|
|
88
|
+
function_kwargs: dict[str, Any] | None = None,
|
|
89
|
+
) -> SimulationEvent:
|
|
90
|
+
"""Schedule event for the current time instant
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
function (Callable): The callable to execute for this event
|
|
94
|
+
priority (Priority): the priority of the event, optional
|
|
95
|
+
function_args (List[Any]): list of arguments for function
|
|
96
|
+
function_kwargs (Dict[str, Any]): dict of keyword arguments for function
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
SimulationEvent: the simulation event that is scheduled
|
|
100
|
+
|
|
101
|
+
"""
|
|
102
|
+
return self.schedule_event_relative(
|
|
103
|
+
function,
|
|
104
|
+
0.0,
|
|
105
|
+
priority=priority,
|
|
106
|
+
function_args=function_args,
|
|
107
|
+
function_kwargs=function_kwargs,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def schedule_event_absolute(
|
|
111
|
+
self,
|
|
112
|
+
function: Callable,
|
|
113
|
+
time: int | float,
|
|
114
|
+
priority: Priority = Priority.DEFAULT,
|
|
115
|
+
function_args: list[Any] | None = None,
|
|
116
|
+
function_kwargs: dict[str, Any] | None = None,
|
|
117
|
+
) -> SimulationEvent:
|
|
118
|
+
"""Schedule event for the specified time instant
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
function (Callable): The callable to execute for this event
|
|
122
|
+
time (int | float): the time for which to schedule the event
|
|
123
|
+
priority (Priority): the priority of the event, optional
|
|
124
|
+
function_args (List[Any]): list of arguments for function
|
|
125
|
+
function_kwargs (Dict[str, Any]): dict of keyword arguments for function
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
SimulationEvent: the simulation event that is scheduled
|
|
129
|
+
|
|
130
|
+
"""
|
|
131
|
+
if self.time > time:
|
|
132
|
+
raise ValueError("trying to schedule an event in the past")
|
|
133
|
+
|
|
134
|
+
event = SimulationEvent(
|
|
135
|
+
time,
|
|
136
|
+
function,
|
|
137
|
+
priority=priority,
|
|
138
|
+
function_args=function_args,
|
|
139
|
+
function_kwargs=function_kwargs,
|
|
140
|
+
)
|
|
141
|
+
self._schedule_event(event)
|
|
142
|
+
return event
|
|
143
|
+
|
|
144
|
+
def schedule_event_relative(
|
|
145
|
+
self,
|
|
146
|
+
function: Callable,
|
|
147
|
+
time_delta: int | float,
|
|
148
|
+
priority: Priority = Priority.DEFAULT,
|
|
149
|
+
function_args: list[Any] | None = None,
|
|
150
|
+
function_kwargs: dict[str, Any] | None = None,
|
|
151
|
+
) -> SimulationEvent:
|
|
152
|
+
"""Schedule event for the current time plus the time delta
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
function (Callable): The callable to execute for this event
|
|
156
|
+
time_delta (int | float): the time delta
|
|
157
|
+
priority (Priority): the priority of the event, optional
|
|
158
|
+
function_args (List[Any]): list of arguments for function
|
|
159
|
+
function_kwargs (Dict[str, Any]): dict of keyword arguments for function
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
SimulationEvent: the simulation event that is scheduled
|
|
163
|
+
|
|
164
|
+
"""
|
|
165
|
+
event = SimulationEvent(
|
|
166
|
+
self.time + time_delta,
|
|
167
|
+
function,
|
|
168
|
+
priority=priority,
|
|
169
|
+
function_args=function_args,
|
|
170
|
+
function_kwargs=function_kwargs,
|
|
171
|
+
)
|
|
172
|
+
self._schedule_event(event)
|
|
173
|
+
return event
|
|
174
|
+
|
|
175
|
+
def cancel_event(self, event: SimulationEvent) -> None:
|
|
176
|
+
"""remove the event from the event list
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
event (SimulationEvent): The simulation event to remove
|
|
180
|
+
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
self.event_list.remove(event)
|
|
184
|
+
|
|
185
|
+
def _schedule_event(self, event: SimulationEvent):
|
|
186
|
+
if not self.check_time_unit(event.time):
|
|
187
|
+
raise ValueError(
|
|
188
|
+
f"time unit mismatch {event.time} is not of time unit {self.time_unit}"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# check timeunit of events
|
|
192
|
+
self.event_list.add_event(event)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class ABMSimulator(Simulator):
|
|
196
|
+
"""This simulator uses incremental time progression, while allowing for additional event scheduling.
|
|
197
|
+
|
|
198
|
+
The basic time unit of this simulator is an integer. It schedules `model.step` for each tick with the
|
|
199
|
+
highest priority. This implies that by default, `model.step` is the first event executed at a specific tick.
|
|
200
|
+
In addition, discrete event scheduling, using integer as the time unit is fully supported, paving the way
|
|
201
|
+
for hybrid ABM-DEVS simulations.
|
|
202
|
+
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
def __init__(self):
|
|
206
|
+
super().__init__(int, 0)
|
|
207
|
+
|
|
208
|
+
def setup(self, model):
|
|
209
|
+
super().setup(model)
|
|
210
|
+
self.schedule_event_now(self.model.step, priority=Priority.HIGH)
|
|
211
|
+
|
|
212
|
+
def check_time_unit(self, time) -> bool:
|
|
213
|
+
if isinstance(time, int):
|
|
214
|
+
return True
|
|
215
|
+
if isinstance(time, float):
|
|
216
|
+
return time.is_integer()
|
|
217
|
+
else:
|
|
218
|
+
return False
|
|
219
|
+
|
|
220
|
+
def schedule_event_next_tick(
|
|
221
|
+
self,
|
|
222
|
+
function: Callable,
|
|
223
|
+
priority: Priority = Priority.DEFAULT,
|
|
224
|
+
function_args: list[Any] | None = None,
|
|
225
|
+
function_kwargs: dict[str, Any] | None = None,
|
|
226
|
+
) -> SimulationEvent:
|
|
227
|
+
"""Schedule a SimulationEvent for the next tick
|
|
228
|
+
|
|
229
|
+
Args
|
|
230
|
+
function (Callable): the callable to execute
|
|
231
|
+
priority (Priority): the priority of the event
|
|
232
|
+
function_args (List[Any]): List of arguments to pass to the callable
|
|
233
|
+
function_kwargs (Dict[str, Any]): List of keyword arguments to pass to the callable
|
|
234
|
+
|
|
235
|
+
"""
|
|
236
|
+
return self.schedule_event_relative(
|
|
237
|
+
function,
|
|
238
|
+
1,
|
|
239
|
+
priority=priority,
|
|
240
|
+
function_args=function_args,
|
|
241
|
+
function_kwargs=function_kwargs,
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
def run_until(self, end_time: int) -> None:
|
|
245
|
+
"""run the simulator up to and included the specified end time
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
end_time (float| int): The end_time delta. The simulator is until the specified end time
|
|
249
|
+
|
|
250
|
+
"""
|
|
251
|
+
while True:
|
|
252
|
+
try:
|
|
253
|
+
event = self.event_list.pop_event()
|
|
254
|
+
except IndexError:
|
|
255
|
+
self.time = end_time
|
|
256
|
+
break
|
|
257
|
+
|
|
258
|
+
if event.time <= end_time:
|
|
259
|
+
self.time = event.time
|
|
260
|
+
if event.fn() == self.model.step:
|
|
261
|
+
self.schedule_event_next_tick(
|
|
262
|
+
self.model.step, priority=Priority.HIGH
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
event.execute()
|
|
266
|
+
else:
|
|
267
|
+
self.time = end_time
|
|
268
|
+
self._schedule_event(event)
|
|
269
|
+
break
|
|
270
|
+
|
|
271
|
+
def run_for(self, time_delta: int):
|
|
272
|
+
"""run the simulator for the specified time delta
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
time_delta (float| int): The time delta. The simulator is run from the current time to the current time
|
|
276
|
+
plus the time delta
|
|
277
|
+
|
|
278
|
+
"""
|
|
279
|
+
end_time = self.time + time_delta - 1
|
|
280
|
+
self.run_until(end_time)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class DEVSimulator(Simulator):
|
|
284
|
+
"""A simulator where the unit of time is a float. Can be used for full-blown discrete event simulating using
|
|
285
|
+
event scheduling.
|
|
286
|
+
|
|
287
|
+
"""
|
|
288
|
+
|
|
289
|
+
def __init__(self):
|
|
290
|
+
super().__init__(float, 0.0)
|
|
291
|
+
|
|
292
|
+
def check_time_unit(self, time) -> bool:
|
|
293
|
+
return isinstance(time, numbers.Number)
|