9
9
from collections import deque
10
10
11
11
import numpy as np
12
-
13
- sys .path .insert (0 , os .path .abspath ("../../mesa" ))
14
12
from mesa .experimental .continuous_space import ContinuousSpaceAgent
15
13
16
14
17
15
class AntibodyAgent (ContinuousSpaceAgent ):
18
16
"""An Antibody agent. They move randomly until they see a virus, go fight it.
19
17
If they lose, stay KO for a bit, lose health and back to random moving.
20
18
"""
19
+ speed = 1.5
20
+ sight_range = 10
21
+ ko_timeout = 15
22
+ memory_capacity = 3
23
+ health = 2
21
24
22
25
def __init__ (
23
26
self ,
24
27
model ,
25
28
space ,
26
- sight_range ,
27
29
duplication_rate ,
28
- ko_timeout ,
29
- memory_capacity ,
30
30
initial_position = (0 , 0 ),
31
31
direction = (1 , 1 ),
32
32
):
33
33
super ().__init__ (model = model , space = space )
34
34
35
- # Movement & state
35
+ # Movement & characteristics
36
36
self .position = initial_position
37
- self .speed = 1.5
38
37
self .direction = np .array (direction , dtype = float )
39
-
40
- # Characteristics
41
- self .sight_range = sight_range
42
- self .health = 2
43
38
self .duplication_rate = duplication_rate
44
39
45
40
# Memory
46
- self .st_memory : deque = deque ()
41
+ self .st_memory : deque = deque (maxlen = self . memory_capacity )
47
42
self .lt_memory : list = []
48
- self .memory_capacity = memory_capacity
49
43
50
44
# Target & KO state
51
45
self .target = None # will hold a weakref.ref or None
52
- self .ko_timeout = ko_timeout
53
46
self .ko_steps_left = 0
54
47
55
48
def step (self ):
49
+ nearby_agents ,_ = self .space .get_agents_in_radius (self .position , self .sight_range )
50
+ nearby_viruses = [a for a in nearby_agents if isinstance (a , VirusAgent )]
51
+ nearby_antibodies = [a for a in nearby_agents if isinstance (a , AntibodyAgent ) and a .unique_id != self .unique_id ]
52
+
56
53
# Acquire a virus target if we don't already have one
57
- if self .target is None :
58
- closest = self .find_closest_virus ()
59
- if closest :
60
- self .target = weakref .ref (closest )
54
+ if self .target is None and nearby_viruses :
55
+ closest = nearby_viruses [0 ]
56
+ self .target = weakref .ref (closest )
61
57
62
58
# Communicate and maybe duplicate
63
- self .communicate ()
59
+ self .communicate (nearby_antibodies )
64
60
if self .random .random () < self .duplication_rate :
65
61
self .duplicate ()
66
62
67
63
# Then move
68
64
self .move ()
69
65
70
- def find_closest_virus (self ):
71
- agents , _ = self .space .get_agents_in_radius (self .position , self .sight_range )
72
- viruses = [a for a in agents if isinstance (a , VirusAgent )]
73
- return viruses [0 ] if viruses else None
74
-
75
- def communicate (self ) -> bool :
76
- agents , _ = self .space .get_agents_in_radius (self .position , self .sight_range )
77
- peers = [
78
- a
79
- for a in agents
80
- if isinstance (a , AntibodyAgent ) and a .unique_id != self .unique_id
81
- ]
82
- if not peers :
83
- return False
84
-
85
- for other in peers :
66
+ def communicate (self , nearby_antibodies ) -> bool :
67
+ for other in nearby_antibodies :
86
68
to_share = [
87
69
dna for dna in self .st_memory if dna and dna not in other .lt_memory
88
70
]
89
71
if to_share :
90
72
other .st_memory .extend (to_share )
91
73
other .lt_memory .extend (to_share )
92
- while len (other .st_memory ) > self .memory_capacity :
93
- other .st_memory .popleft ()
94
74
return True
95
75
96
76
def duplicate (self ):
97
77
clone = AntibodyAgent (
98
78
self .model ,
99
79
self .space ,
100
- sight_range = self .sight_range ,
101
80
duplication_rate = self .duplication_rate ,
102
- ko_timeout = self .ko_timeout ,
103
- memory_capacity = self .memory_capacity ,
104
81
initial_position = self .position ,
105
82
direction = self .direction ,
106
83
)
107
84
# Copy over memory
108
- clone .st_memory = deque (item for item in self .st_memory if item )
85
+ clone .st_memory = deque (maxlen = self .memory_capacity )
86
+ clone .st_memory .extend ([item for item in self .st_memory if item ])
109
87
clone .lt_memory = [item for item in self .lt_memory if item ]
110
88
clone .target = None
111
89
clone .ko_steps_left = 0
112
90
113
- self .model .antibodies_set .add (clone )
114
-
115
91
def move (self ):
116
- # If we've been removed from the space, bail out
117
- if getattr (self , "space" , None ) is None :
118
- return
119
92
120
93
# Dereference weakref if needed
121
94
target = (
@@ -163,22 +136,17 @@ def move(self):
163
136
self .position = new_pos
164
137
165
138
def engage_virus (self , virus ) -> str :
166
- # If it's already gone
167
- if virus not in self .model .agents :
168
- self .target = None
169
- return "no_target"
170
139
171
140
dna = copy .deepcopy (virus .dna )
172
141
if dna in self .st_memory or dna in self .lt_memory :
173
142
virus .remove ()
174
143
self .target = None
175
- return "win"
144
+
176
145
else :
177
146
# KO (or death)
178
147
self .health -= 1
179
148
if self .health <= 0 :
180
149
self .remove ()
181
- return "dead"
182
150
183
151
self .st_memory .append (dna )
184
152
self .lt_memory .append (dna )
@@ -190,6 +158,7 @@ def engage_virus(self, virus) -> str:
190
158
191
159
class VirusAgent (ContinuousSpaceAgent ):
192
160
"""A virus agent: random movement, mutation, duplication, passive to antibodies."""
161
+ speed = 1
193
162
194
163
def __init__ (
195
164
self ,
@@ -205,28 +174,23 @@ def __init__(
205
174
self .position = position
206
175
self .mutation_rate = mutation_rate
207
176
self .duplication_rate = duplication_rate
208
- self .speed = 1
209
177
self .direction = np .array ((1 , 1 ), dtype = float )
210
178
self .dna = dna if dna is not None else self .generate_dna ()
211
179
212
180
def step (self ):
213
- # If already removed from the space, don't do anything
214
- if getattr (self , "space" , None ) is None :
215
- return
216
181
if self .random .random () < self .duplication_rate :
217
182
self .duplicate ()
218
183
self .move ()
219
184
220
185
def duplicate (self ):
221
- clone = VirusAgent (
186
+ VirusAgent (
222
187
self .model ,
223
188
self .space ,
224
189
mutation_rate = self .mutation_rate ,
225
190
duplication_rate = self .duplication_rate ,
226
191
position = self .position ,
227
192
dna = self .generate_dna (self .dna ),
228
193
)
229
- self .model .viruses_set .add (clone )
230
194
231
195
def generate_dna (self , dna = None ):
232
196
if dna is None :
@@ -240,9 +204,6 @@ def generate_dna(self, dna=None):
240
204
return dna
241
205
242
206
def move (self ):
243
- if getattr (self , "space" , None ) is None :
244
- return
245
-
246
207
# Random walk
247
208
perturb = np .array (
248
209
[
@@ -255,5 +216,4 @@ def move(self):
255
216
if norm > 0 :
256
217
self .direction /= norm
257
218
258
- # Step
259
- self .position = self .position + self .direction * self .speed
219
+ self .position += self .direction * self .speed
0 commit comments