forked from projectmesa/mesa
-
Notifications
You must be signed in to change notification settings - Fork 0
/
test_grid.py
536 lines (431 loc) · 16.4 KB
/
test_grid.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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
"""
Test the Grid objects.
"""
import random
import unittest
from unittest.mock import patch, Mock
from mesa.space import Grid, SingleGrid, MultiGrid, HexGrid
# Initial agent positions for testing
#
# --- visual aid ----
# 0 0 0
# 1 1 0
# 0 1 0
# 1 0 1
# 0 0 1
# -------------------
TEST_GRID = [[0, 1, 0, 1, 0, 0], [0, 0, 1, 1, 0, 1], [1, 1, 0, 0, 0, 1]]
class MockAgent:
"""
Minimalistic agent for testing purposes.
"""
def __init__(self, unique_id, pos):
self.random = random.Random(0)
self.unique_id = unique_id
self.pos = pos
class TestBaseGrid(unittest.TestCase):
"""
Testing a non-toroidal grid.
"""
torus = False
def setUp(self):
"""
Create a test non-toroidal grid and populate it with Mock Agents
"""
# The height needs to be even to test the edge case described in PR #1517
height = 6 # height of grid
width = 3 # width of grid
self.grid = Grid(width, height, self.torus)
self.agents = []
counter = 0
for x in range(width):
for y in range(height):
if TEST_GRID[x][y] == 0:
continue
counter += 1
# Create and place the mock agent
a = MockAgent(counter, None)
self.agents.append(a)
self.grid.place_agent(a, (x, y))
def test_agent_positions(self):
"""
Ensure that the agents are all placed properly.
"""
for agent in self.agents:
x, y = agent.pos
assert self.grid[x][y] == agent
def test_cell_agent_reporting(self):
"""
Ensure that if an agent is in a cell, get_cell_list_contents accurately
reports that fact.
"""
for agent in self.agents:
x, y = agent.pos
assert agent in self.grid.get_cell_list_contents([(x, y)])
def test_listfree_cell_agent_reporting(self):
"""
Ensure that if an agent is in a cell, get_cell_list_contents accurately
reports that fact, even when single position is not wrapped in a list.
"""
for agent in self.agents:
x, y = agent.pos
assert agent in self.grid.get_cell_list_contents((x, y))
def test_iter_cell_agent_reporting(self):
"""
Ensure that if an agent is in a cell, iter_cell_list_contents
accurately reports that fact.
"""
for agent in self.agents:
x, y = agent.pos
assert agent in self.grid.iter_cell_list_contents([(x, y)])
def test_listfree_iter_cell_agent_reporting(self):
"""
Ensure that if an agent is in a cell, iter_cell_list_contents
accurately reports that fact, even when single position is not
wrapped in a list.
"""
for agent in self.agents:
x, y = agent.pos
assert agent in self.grid.iter_cell_list_contents((x, y))
def test_neighbors(self):
"""
Test the base neighborhood methods on the non-toroid.
"""
neighborhood = self.grid.get_neighborhood((1, 1), moore=True)
assert len(neighborhood) == 8
neighborhood = self.grid.get_neighborhood((1, 4), moore=False)
assert len(neighborhood) == 4
neighborhood = self.grid.get_neighborhood((1, 4), moore=True)
assert len(neighborhood) == 8
neighborhood = self.grid.get_neighborhood((0, 0), moore=False)
assert len(neighborhood) == 2
neighbors = self.grid.get_neighbors((4, 1), moore=False)
assert len(neighbors) == 0
neighbors = self.grid.get_neighbors((4, 1), moore=True)
assert len(neighbors) == 0
neighbors = self.grid.get_neighbors((1, 1), moore=False, include_center=True)
assert len(neighbors) == 3
neighbors = self.grid.get_neighbors((1, 3), moore=False, radius=2)
assert len(neighbors) == 3
def test_coord_iter(self):
ci = self.grid.coord_iter()
# no agent in first space
first = next(ci)
assert first[0] is None
assert first[1] == 0
assert first[2] == 0
# first agent in the second space
second = next(ci)
assert second[0].unique_id == 1
assert second[0].pos == (0, 1)
assert second[1] == 0
assert second[2] == 1
def test_agent_move(self):
# get the agent at [0, 1]
agent = self.agents[0]
self.grid.move_agent(agent, (1, 1))
assert agent.pos == (1, 1)
# move it off the torus and check for the exception
if not self.torus:
with self.assertRaises(Exception):
self.grid.move_agent(agent, [-1, 1])
with self.assertRaises(Exception):
self.grid.move_agent(agent, [1, self.grid.height + 1])
else:
self.grid.move_agent(agent, [-1, 1])
assert agent.pos == (self.grid.width - 1, 1)
self.grid.move_agent(agent, [1, self.grid.height + 1])
assert agent.pos == (1, 1)
def test_agent_remove(self):
agent = self.agents[0]
x, y = agent.pos
self.grid.remove_agent(agent)
assert agent.pos is None
assert self.grid.grid[x][y] is None
def test_swap_pos(self):
# Swap agents positions
agent_a, agent_b = list(filter(None, self.grid))[:2]
pos_a = agent_a.pos
pos_b = agent_b.pos
self.grid.swap_pos(agent_a, agent_b)
assert agent_a.pos == pos_b
assert agent_b.pos == pos_a
assert self.grid[pos_a] == agent_b
assert self.grid[pos_b] == agent_a
# Swap the same agents
self.grid.swap_pos(agent_a, agent_a)
assert agent_a.pos == pos_b
assert self.grid[pos_b] == agent_a
# Raise for agents not on the grid
self.grid.remove_agent(agent_a)
self.grid.remove_agent(agent_b)
id_a = agent_a.unique_id
id_b = agent_b.unique_id
e_message = f"<Agent id: {id_a}>, <Agent id: {id_b}> - not on the grid"
with self.assertRaisesRegex(Exception, e_message):
self.grid.swap_pos(agent_a, agent_b)
class TestBaseGridTorus(TestBaseGrid):
"""
Testing the toroidal base grid.
"""
torus = True
def test_neighbors(self):
"""
Test the toroidal neighborhood methods.
"""
neighborhood = self.grid.get_neighborhood((1, 1), moore=True)
assert len(neighborhood) == 8
neighborhood = self.grid.get_neighborhood((1, 4), moore=True)
assert len(neighborhood) == 8
neighborhood = self.grid.get_neighborhood((0, 0), moore=False)
assert len(neighborhood) == 4
# here we test the edge case described in PR #1517 using a radius
# measuring half of the grid height
neighborhood = self.grid.get_neighborhood((0, 0), moore=True, radius=3)
assert len(neighborhood) == 17
neighborhood = self.grid.get_neighborhood((1, 1), moore=False, radius=3)
assert len(neighborhood) == 15
neighbors = self.grid.get_neighbors((1, 4), moore=False)
assert len(neighbors) == 2
neighbors = self.grid.get_neighbors((1, 4), moore=True)
assert len(neighbors) == 4
neighbors = self.grid.get_neighbors((1, 1), moore=False, include_center=True)
assert len(neighbors) == 3
neighbors = self.grid.get_neighbors((1, 3), moore=False, radius=2)
assert len(neighbors) == 3
class TestSingleGrid(unittest.TestCase):
"""
Test the SingleGrid object.
Since it inherits from Grid, all the functionality tested above should
work here too. Instead, this tests the enforcement.
"""
def setUp(self):
"""
Create a test non-toroidal grid and populate it with Mock Agents
"""
width = 3
height = 5
self.grid = SingleGrid(width, height, True)
self.agents = []
counter = 0
for x in range(width):
for y in range(height):
if TEST_GRID[x][y] == 0:
continue
counter += 1
# Create and place the mock agent
a = MockAgent(counter, None)
self.agents.append(a)
self.grid.place_agent(a, (x, y))
self.num_agents = len(self.agents)
@patch.object(MockAgent, "model", create=True)
def test_position_agent(self, mock_model):
a = MockAgent(100, None)
with self.assertRaises(Exception) as exc_info:
self.grid.position_agent(a, (1, 1))
expected = (
"x must be an integer or a string 'random'."
" Actual type: <class 'tuple'>. Actual value: (1, 1)."
)
assert str(exc_info.exception) == expected
with self.assertRaises(Exception) as exc_info:
self.grid.position_agent(a, "(1, 1)")
expected = (
"x must be an integer or a string 'random'."
" Actual type: <class 'str'>. Actual value: (1, 1)."
)
assert str(exc_info.exception) == expected
self.grid.position_agent(a, "random")
@patch.object(MockAgent, "model", create=True)
def test_enforcement(self, mock_model):
"""
Test the SingleGrid empty count and enforcement.
"""
assert len(self.grid.empties) == 9
a = MockAgent(100, None)
with self.assertRaises(Exception):
self.grid.place_agent(a, (0, 1))
# Place the agent in an empty cell
mock_model.schedule.get_agent_count = Mock(side_effect=lambda: len(self.agents))
self.grid.position_agent(a)
self.num_agents += 1
# Test whether after placing, the empty cells are reduced by 1
assert a.pos not in self.grid.empties
assert len(self.grid.empties) == 8
for i in range(10):
self.grid.move_to_empty(a, num_agents=self.num_agents)
assert len(self.grid.empties) == 8
# Place agents until the grid is full
empty_cells = len(self.grid.empties)
for i in range(empty_cells):
a = MockAgent(101 + i, None)
self.grid.position_agent(a)
self.num_agents += 1
assert len(self.grid.empties) == 0
a = MockAgent(110, None)
with self.assertRaises(Exception):
self.grid.position_agent(a)
with self.assertRaises(Exception):
self.move_to_empty(self.agents[0], num_agents=self.num_agents)
# Number of agents at each position for testing
# Initial agent positions for testing
#
# --- visual aid ----
# 0 0 0
# 2 0 3
# 0 5 0
# 1 1 0
# 0 0 0
# -------------------
TEST_MULTIGRID = [[0, 1, 0, 2, 0], [0, 1, 5, 0, 0], [0, 0, 0, 3, 0]]
class TestMultiGrid(unittest.TestCase):
"""
Testing a toroidal MultiGrid
"""
torus = True
def setUp(self):
"""
Create a test non-toroidal grid and populate it with Mock Agents
"""
width = 3
height = 5
self.grid = MultiGrid(width, height, self.torus)
self.agents = []
counter = 0
for x in range(width):
for y in range(height):
for i in range(TEST_MULTIGRID[x][y]):
counter += 1
# Create and place the mock agent
a = MockAgent(counter, None)
self.agents.append(a)
self.grid.place_agent(a, (x, y))
def test_agent_positions(self):
"""
Ensure that the agents are all placed properly on the MultiGrid.
"""
for agent in self.agents:
x, y = agent.pos
assert agent in self.grid[x][y]
def test_neighbors(self):
"""
Test the toroidal MultiGrid neighborhood methods.
"""
neighborhood = self.grid.get_neighborhood((1, 1), moore=True)
assert len(neighborhood) == 8
neighborhood = self.grid.get_neighborhood((1, 4), moore=True)
assert len(neighborhood) == 8
neighborhood = self.grid.get_neighborhood((0, 0), moore=False)
assert len(neighborhood) == 4
neighbors = self.grid.get_neighbors((1, 4), moore=False)
assert len(neighbors) == 0
neighbors = self.grid.get_neighbors((1, 4), moore=True)
assert len(neighbors) == 5
neighbors = self.grid.get_neighbors((1, 1), moore=False, include_center=True)
assert len(neighbors) == 7
neighbors = self.grid.get_neighbors((1, 3), moore=False, radius=2)
assert len(neighbors) == 11
class TestHexGrid(unittest.TestCase):
"""
Testing a hexagonal grid.
"""
def setUp(self):
"""
Create a test non-toroidal grid and populate it with Mock Agents
"""
width = 3
height = 5
self.grid = HexGrid(width, height, torus=False)
self.agents = []
counter = 0
for x in range(width):
for y in range(height):
if TEST_GRID[x][y] == 0:
continue
counter += 1
# Create and place the mock agent
a = MockAgent(counter, None)
self.agents.append(a)
self.grid.place_agent(a, (x, y))
def test_neighbors(self):
"""
Test the hexagonal neighborhood methods on the non-toroid.
"""
neighborhood = self.grid.get_neighborhood((1, 1))
assert len(neighborhood) == 6
neighborhood = self.grid.get_neighborhood((0, 2))
assert len(neighborhood) == 4
neighborhood = self.grid.get_neighborhood((1, 0))
assert len(neighborhood) == 3
neighborhood = self.grid.get_neighborhood((1, 4))
assert len(neighborhood) == 5
neighborhood = self.grid.get_neighborhood((0, 4))
assert len(neighborhood) == 2
neighborhood = self.grid.get_neighborhood((0, 0))
assert len(neighborhood) == 3
neighborhood = self.grid.get_neighborhood((1, 1), include_center=True)
assert len(neighborhood) == 7
neighborhood = self.grid.get_neighborhood((0, 0), radius=4)
assert len(neighborhood) == 13
assert sum(x + y for x, y in neighborhood) == 39
class TestHexGridTorus(TestBaseGrid):
"""
Testing a hexagonal toroidal grid.
"""
torus = True
def setUp(self):
"""
Create a test non-toroidal grid and populate it with Mock Agents
"""
width = 3
height = 5
self.grid = HexGrid(width, height, torus=True)
self.agents = []
counter = 0
for x in range(width):
for y in range(height):
if TEST_GRID[x][y] == 0:
continue
counter += 1
# Create and place the mock agent
a = MockAgent(counter, None)
self.agents.append(a)
self.grid.place_agent(a, (x, y))
def test_neighbors(self):
"""
Test the hexagonal neighborhood methods on the toroid.
"""
neighborhood = self.grid.get_neighborhood((1, 1))
assert len(neighborhood) == 6
neighborhood = self.grid.get_neighborhood((1, 1), include_center=True)
assert len(neighborhood) == 7
neighborhood = self.grid.get_neighborhood((0, 0))
assert len(neighborhood) == 6
neighborhood = self.grid.get_neighborhood((2, 4))
assert len(neighborhood) == 6
neighborhood = self.grid.get_neighborhood((1, 1), include_center=True, radius=2)
assert len(neighborhood) == 13
neighborhood = self.grid.get_neighborhood((0, 0), radius=4)
assert len(neighborhood) == 14
assert sum(x + y for x, y in neighborhood) == 45
class TestIndexing:
# Create a grid where the content of each coordinate is a tuple of its coordinates
grid = Grid(3, 5, True)
for _, x, y in grid.coord_iter():
grid.grid[x][y] = (x, y)
def test_int(self):
assert self.grid[0][0] == (0, 0)
def test_tuple(self):
assert self.grid[1, 1] == (1, 1)
def test_list(self):
assert self.grid[(0, 0), (1, 1)] == [(0, 0), (1, 1)]
assert self.grid[(0, 0), (5, 3)] == [(0, 0), (2, 3)]
def test_torus(self):
assert self.grid[3, 5] == (0, 0)
def test_slice(self):
assert self.grid[:, 0] == [(0, 0), (1, 0), (2, 0)]
assert self.grid[::-1, 0] == [(2, 0), (1, 0), (0, 0)]
assert self.grid[1, :] == [(1, 0), (1, 1), (1, 2), (1, 3), (1, 4)]
assert self.grid[:, :] == [(x, y) for x in range(3) for y in range(5)]
if __name__ == "__main__":
unittest.main()