diff --git a/simra.py b/simra.py index c9da6ea0ea1ea0acdc5df4b45d152d135e98c14c..4c9cf3807a2e339940baba1b61f2a8916f7c659f 100755 --- a/simra.py +++ b/simra.py @@ -4,42 +4,45 @@ import collections flatten = lambda l: [item for sublist in l for item in sublist] -# TODO: History should also store UP -# TODO: Store and enforce node dependencies - class Mergeable_dict(dict): - """Like dict, except that foo += bar update all entries of foo with the -corresponding values in bar. This requires that bar.keys() is a subset of -foo.keys(), otherwise IndexError is raised.""" + """ + Subclass of dict, except that mergeable_dict += dict shall update all + entries of foo with the corresponding values in bar. + This requires that bar.keys() is a subset of foo.keys(), otherwise + IndexError is raised. + """ def __iadd__(self, other): for k in other.keys(): if not k in self.keys(): raise IndexError() self[k] = other[k] return self +# BUG: changing the UP manually (not inc/dec) will break the assumption +# that an.unstep(); an.step() is no-op. +# The other way around, an.step(); an.unstep() is always working though. + class AN: """ An automata network. Fields: - nodes -- a set node "names", that can be arbitrary (hashable) values - dependencies -- a dict mapping nodes to lists/sets of nodes, on which it - depends for the update - update_funcs -- a dict mapping nodes to functions ; each function takes a - dictionary (neighbour node→current value) as a unique - parameter + nodes -- set node "names", that can be arbitrary (hashable) values + dependencies -- dict mapping nodes to lists/sets of nodes, encoding the + dependency graph of the network + update_funcs -- dict mapping nodes to functions; each function takes a + dictionary (neighbour node→current value) as sole argument (it is possible to do my_an.update_funcs += dict to merge in a dictionary, i.e. to change the functions of only some nodes) - update_mode -- a list of sets (or list of lists) interpreted as a - periodic update mode - init_state -- a dict from nodes to values, that tells which values should - be used when the network is (re)initialized - state -- a dict nodes→values holding the current state (value of each + update_mode -- list of sets (or list of lists) interpreted as a periodic + update mode + init_state -- dict mapping nodes to values, that tells which values + should be used when the network is (re)initialized + state -- dict nodes→values holding the current state (value of each node) of the network - history -- a circular buffer of the last self.history_len states + history -- circular buffer of the last self.history_len states traversed by the network - up -- (Update Pointer) is an index for self.update_mode ; it + up -- Update Pointer; an index for self.update_mode; it “points” to the next update to do """ def __init__(self, nodes, dependencies, update_funcs, update_mode, init_state=None, history_len=10): @@ -47,7 +50,7 @@ class AN: self.dependencies = dependencies self.update_funcs = update_funcs self.update_mode = update_mode - self.init_state = init_state + self.init_state = init_state # None is properly handled by a property self.reinit_state() # Sets self.state, self.up self.history = collections.deque(maxlen=history_len) @@ -66,7 +69,7 @@ class AN: def set_multiple_update_funcs(self, new_update_funcs): """ - If update_funcs is a dictionary node->function, change the update + If update_funcs is a dictionary node→function, change the update function of each node to the corresponding function. All nodes in update_funcs.keys() need to exist; but not all nodes from the network need to be changed by this operation. @@ -96,17 +99,17 @@ class AN: return self._update_mode @update_mode.setter - def update_mode(self, update_mode): + def update_mode(self, new_update_mode): """ Change the update mode. update_mode needs to be a list of sets (or list of lists) and is interpreted as a periodic update mode. - Changing the update_mode clears the state history to ensure that - an.unstep() followed an.step() is no-op (or raises an exception). + This resets up to 0 and clears the history as side-effects. """ - if not set(flatten(update_mode)).issubset(self.nodes): + if not set(flatten(new_update_mode)).issubset(self.nodes): raise ValueError("update_mode tries to update a nonexistent node") - self._update_mode = update_mode + self._update_mode = new_update_mode + self.up = 0 self.history.clear() @property @@ -114,7 +117,7 @@ class AN: return self._init_state @init_state.setter - def init_state(self, init_state): + def init_state(self, new_init_state): """ Change initial state. Initial state is used when reinitializing the network. @@ -122,11 +125,11 @@ class AN: must be a dictionary node->value, where init_state.keys() is equal to the set of nodes of the network. """ - if init_state: - if set(init_state.keys()) != self.nodes: - raise ValueError("init_state either misses state for a node or has a sate for a nonexistent node") - else: - self._init_state = init_state.copy() + if new_init_state: + if set(new_init_state.keys()) != self.nodes: + raise ValueError("""init_state either misses state for a node +or has a sate for a nonexistent node""") + self._init_state = new_init_state.copy() else: self._init_state = {} for n in self.nodes: @@ -147,7 +150,7 @@ class AN: @history_len.setter def history_len(self, new_history_len): new_history = collections.deque(new_history_len) - for i in range(min(history_len, self.history_len)): + for i in range(min(new_history_len, self.history_len)): new_history.append(self.history[-(i+1)]) self.history = new_history @@ -165,7 +168,8 @@ class AN: def step(self): """Execute one step of the AN.""" new_state = self.state.copy() - for n in self.update_mode[self.up]: + for node in self.update_mode[self.up]: + local_view = dict((neighbour, self.state[neighbour]) for neighbour in self.dependencies[node]) new_state[n] = (self.update_func[n])(self.state) self.history.append(self.state) @@ -182,6 +186,15 @@ class AN: self.state = self.history.pop() self.up -= 1 + # ################################################################ + + def __iter__(self): + return self.nodes + + def __call__(self): + self.step() + return self.state.copy() + # ################################################################ def button_press(event):