1
+ # -*- coding: utf-8 -*-
2
+
3
+ """ elements/bot/manager.py
4
+ """
5
+
6
+ from ..core import Core
7
+
1
8
import pickle
2
9
import numpy as np
3
10
from collections import defaultdict
4
- from numpy import array , ndarray , int8 , uint8 , float16
5
-
6
- from ..core import Core
7
11
8
12
# global wait time for agents
9
- WAIT_TIME : uint8 = 64
13
+ WAIT_TIME : np . uint8 = 64
10
14
11
15
class Amoebot (Core ):
12
- r""" core amobeot functionalities, extended using algorithmic modules
16
+ r"""
17
+ Core amobeot functionalities, extended using algorithmic modules.
18
+
19
+ Attributes
20
+
21
+ __id (numpy.uint8) :: unique particle identifier.
22
+ head (numpy.ndarray) :: position (x and y co-ordinates) of amoebot head.
23
+ tail (numpy.ndarray) :: position (x and y co-ordinates) of amoebot tail.
24
+ labels (numpy.ndarray) :: lookup directions in grid from port labels.
25
+ h_neigbor_status (dict) :: status of the amoebot's neighbourhood around
26
+ its head.
27
+ t_neigbor_status (dict) :: status of the amoebot's neighbourhood around
28
+ its tail.
29
+ mu (numpy.uint8) :: rate parameter for Poisson clock.
30
+ tau0 (numpy.float16) :: temperature parameter for compression.
31
+ cflag (numpy.uint8) :: status flag for compression algorithm.
32
+ active (bool) :: activation status of the amoebot
33
+ clock (numpy.uint8) :: time on the Poisson clock.
34
+
13
35
"""
14
36
15
- def __init__ (self , __id :uint8 , head :array , tail :array = None ):
37
+ def __init__ (self , __id :np .uint8 , head :np .ndarray , tail :np .ndarray = None ):
38
+ r"""
39
+ Attributes
40
+
41
+ __id (numpy.uint8) :: unique particle identifier.
42
+ head (numpy.ndarray) :: position (x and y co-ordinates) of amoebot head.
43
+ tail (numpy.ndarray) default: None :: position (x and y co-ordinates) of
44
+ amoebot tail.
45
+ """
16
46
17
47
# bots should be locally indistinguishable, this name-mangled
18
- # variable is accesible only to the manager
19
- self .__id :uint8 = __id
48
+ # variable is accesible only to the `AmoebotManager` instance.
49
+ self .__id :np . uint8 = __id
20
50
21
51
# head of the bot
22
- self .head :array = head
52
+ self .head :np . ndarray = head
23
53
24
54
# tail of the bot, initially same as head
25
- self .tail :array = head if tail is None else tail
55
+ self .tail :np . ndarray = head if tail is None else tail
26
56
27
57
# lookup/map for port labels to the general grid direction
28
- self .labels :dict = dict ([( uint8 ( ix ), None ) for ix in range ( 6 )] )
58
+ self .labels :dict = self . _reset_neighbourhood_map ( )
29
59
30
60
# status of the bot's neighbourhood
31
- self .h_neighbor_status = dict ([( uint8 ( ix ), None ) for ix in range ( 6 )] )
32
- self .t_neighbor_status = dict ([( uint8 ( ix ), None ) for ix in range ( 6 )] )
61
+ self .h_neighbor_status : dict = self . _reset_neighbourhood_map ( )
62
+ self .t_neighbor_status : dict = self . _reset_neighbourhood_map ( )
33
63
34
- # rate parameter for poisson clock
35
- self .mu :uint8 = uint8 (1 )
64
+ # rate parameter for Poisson clock
65
+ self .mu :np . uint8 = np . uint8 (1 )
36
66
37
- # compression parameter
38
- self .lam : float16 = float16 (5 . )
67
+ # temperature parameter for compression
68
+ self .tau0 : np . float16 = np . float16 (1 . )
39
69
40
70
# status flag for compression algorithm
41
- self .cflag :uint8 = uint8 (0 )
71
+ self .cflag :np . uint8 = np . uint8 (0 )
42
72
43
73
# activation status, true means the bot is active
44
- self .active , self .clock = self ._reset_clock (refresh = True )
74
+ # self.active, self.clock = self._reset_clock(refresh=True)
45
75
46
76
# wait timer for agents
47
77
# self.wait: uint8 = WAIT_TIME
48
78
49
- def orient (self , nmap :defaultdict = None ):
50
- r""" orients bot in clockwise ordering of ports starting at random
79
+ def orient (self , __nmap :defaultdict = None ):
80
+ r"""
81
+ Orients bot in clockwise ordering of ports starting at random by
82
+ labelling each port.
83
+
84
+ NOTE this construction violates the basic constraint of the amoebot
85
+ model that particles have no sense of direction; but is kept for
86
+ convenience.
87
+
88
+ Attributes
89
+ __nmap (defaultdict) default: None :: a dictionary of dictionaries
90
+ used to index nodes using x and y co-odrinates.
51
91
"""
52
92
53
93
# ports labellings of the node
54
- node_ports = array (['n' , 'ne' , 'se' ,
55
- 's' , 'sw' , 'nw'
56
- ], dtype = '<U2' )
94
+ node_ports = np .array (['n' , 'ne' , 'se' , 's' , 'sw' , 'nw' ], dtype = '<U2' )
57
95
58
96
# choose a port at random
59
97
choice = np .random .choice (node_ports )
@@ -64,63 +102,115 @@ def orient(self, nmap:defaultdict=None):
64
102
self .labels [ix ] = node_ports [choice_ix % 6 ]
65
103
choice_ix += 1
66
104
67
- if nmap is not None : self .generate_neighbourhood_map (nmap )
105
+ if __nmap is not None : self .generate_neighbourhood_map (__nmap )
68
106
69
- def generate_neighbourhood_map (self , nmap :defaultdict ):
107
+ def generate_neighbourhood_map (self , __nmap :defaultdict ):
70
108
r"""
71
109
Creates a dictionary of neighbourhood statuses around the head
72
110
(and tail) particles.
111
+
112
+ Attributes
113
+ __nmap (defaultdict) :: a dictionary of dictionaries
114
+ used to index nodes using x and y co-ordinates.
73
115
"""
116
+
117
+ # map the neighbourhood around the particle head
74
118
for port in self .h_neighbor_status :
75
- node = nmap [self .head [0 ]][self .head [1 ]]
119
+ node = __nmap [self .head [0 ]][self .head [1 ]]
120
+
121
+ # get the position of neighbour on current port
76
122
n_position = node .neighbors [self .labels [port ]]
77
123
78
124
# mark the neighbourhood status except own tail
79
125
if np .any (n_position != self .tail ):
80
126
if n_position is not None :
81
- neighbor = nmap [n_position [0 ]][n_position [1 ]]
127
+ neighbor = __nmap [n_position [0 ]][n_position [1 ]]
82
128
self .h_neighbor_status [port ] = neighbor .get_occupancy_status
83
- else : self . h_neighbor_status [ port ] = int8 ( 5 )
84
- else : self .h_neighbor_status [port ] = int8 (- 1 )
129
+ # mark own tail with (-1 )
130
+ else : self .h_neighbor_status [port ] = np . int8 (- 1 )
85
131
86
132
# if this is an expanded particle, also map the tail
87
133
if not self ._is_contracted :
88
134
for port in self .t_neighbor_status :
89
- node = nmap [self .tail [0 ]][self .tail [1 ]]
135
+ node = __nmap [self .tail [0 ]][self .tail [1 ]]
136
+
137
+ # get the position of neighbour on current port
90
138
n_position = node .neighbors [self .labels [port ]]
91
139
92
140
# mark the neighbourhood status except own tail
93
141
if np .any (n_position != self .head ):
94
142
if n_position is not None :
95
- neighbor = nmap [n_position [0 ]][n_position [1 ]]
143
+ neighbor = __nmap [n_position [0 ]][n_position [1 ]]
96
144
self .t_neighbor_status [port ] = \
97
145
neighbor .get_occupancy_status
98
- else : self .t_neighbor_status [port ] = int8 (5 )
99
- else : self .t_neighbor_status [port ] = int8 (- 1 )
100
- # else reset the tail to null state
101
- else : self .t_neighbor_status = dict ([(ix , None ) for ix in range (6 )])
102
-
103
- def open_ports (self , scan_tail :bool = False ) -> list :
104
- r""" a list of open ports for bot to move to
146
+ # mark own head with (-1)
147
+ else : self .t_neighbor_status [port ] = np .int8 (- 1 )
148
+ # else clear the tail neigbourhood status flags
149
+ else : self .t_neighbor_status = self ._reset_neighbourhood_map ()
150
+
151
+ def _neighborhood_contraction_status (
152
+ self ,
153
+ scan_tail :bool = False
154
+ ) -> np .ndarray :
155
+ r"""
156
+ Find the contraction status of the neighbourhood, a list that is
157
+ 1 when a neighbouring node is unoccupied, contracted or occupied by a
158
+ tail and 0 when occupied by an expanded head particle.
159
+
160
+ Attributes
161
+ scan_tail (bool) :: if true, scan the tail; else scan the particle
162
+ head.
163
+
164
+ Return (np.ndarray): a list of contraction statuses in neighbouring
165
+ positions.
105
166
"""
167
+
168
+ scan = self .t_neighbor_status if scan_tail else self .h_neighbor_status
169
+ return np .array ([status != 2 for _ , status in scan .items ()])
170
+
171
+ def open_ports (self , scan_tail :bool = False ) -> np .ndarrary :
172
+ r"""
173
+ Scan the particle neighbourhood for ports to expand into.
174
+
175
+ Attributes
176
+ scan_tail (bool) default: False :: if true, scan the tail; else scan
177
+ the particle head.
178
+
179
+ Return (numpy.ndarrary): a list of open or available ports.
180
+ """
181
+
106
182
open_port_list = list ()
107
183
scan = self .t_neighbor_status if scan_tail else self .h_neighbor_status
108
184
109
185
for port in scan :
110
186
status = scan [port ]
111
- if status == 0 :
187
+
188
+ # nodes with status 0 are unoccupied
189
+ if status is np .uint8 (0 ):
112
190
open_port_list .append (port )
113
191
114
- return array (open_port_list )
192
+ return np .array (open_port_list )
193
+
194
+ def _reset_neighbourhood_map (self ) -> dict :
195
+ r"""
196
+ Return (dict): dictionary with port labels as keys and `None` values.
197
+ """
198
+ return dict ([(ix , None ) for ix in range (6 )])
115
199
116
- def _reset_clock (self , refresh :bool ) -> bool :
200
+ def _reset_clock (self , refresh :bool ) -> tuple :
117
201
r"""
202
+ Set or reset the amoebots Poisson clock whenever the particle execution
203
+ occurs.
204
+
205
+ Attributes
206
+ refresh (bool) :: refresh the clock activation if true.
207
+
208
+ Returns (tuple): 2-tuple with activation status (bool) and value of the
209
+ Poisson clock.
118
210
"""
119
211
if refresh :
120
- self .clock = uint8 (np .random .poisson (lam = self .mu ))
121
-
212
+ self .clock = np .uint8 (np .random .poisson (lam = self .mu ))
122
213
else : self .clock -= 1
123
-
124
214
return (False , self .clock ) if self .clock else (True , self .clock )
125
215
126
216
@property
@@ -143,6 +233,6 @@ def unpickled(cls, data:bytes): return pickle.loads(data)
143
233
144
234
def execute (self ): raise NotImplementedError
145
235
146
- def move_agent (self , ** kwargs ): raise NotImplementedError
236
+ def _move (self , ** kwargs ): raise NotImplementedError
147
237
148
- def compress_agent (self , ** kwargs ): raise NotImplementedError
238
+ def _compress (self , ** kwargs ): raise NotImplementedError
0 commit comments