diff --git a/simra.py b/simra.py index 9bfb3b0c4815b755eb3bd4d337a07b2e054be0c3..8ae434af1bab4e594aa135cd2c9eb19aaf93a32b 100755 --- a/simra.py +++ b/simra.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -import collections -import copy +from collections import deque +from copy import deepcopy from functools import reduce from itertools import count import tkinter as TK @@ -8,6 +8,19 @@ import tkinter as TK # ################################################################ # General utilities +class Gensym: + """ + Poor man's closure to generate unique names. + """ + def __init__(self, prefix="g", seq=count()): + self.prefix = prefix + self.seq = seq + def __call__(self): + return "{}{}".format(self.prefix, next(self.seq)) + +gensym = Gensym() +# gensym() returns successively "g0", "g1", "g2", etc. + def dict_filter(dictionary, keys): """ Return a subdictionary of `dictionary` comprising only the given `keys`. @@ -21,6 +34,9 @@ def integers(n): return list(range(n)) def flatten(l): + """ + Given a list of list, return the list of elements of sublists. + """ return [item for sublist in l for item in sublist] # ################################################################ @@ -32,6 +48,7 @@ class Node: Constructor fields: name -- Any hashable value, used for printing and identification + (must be unique inside an AN or all hell breaks loose) update_func -- Update Function; it must take one parameter: a dictionary mapping name of neighbours to their values dependencies -- List of dependency names (other nodes' names) @@ -40,7 +57,6 @@ class Node: Other fields: state -- Curent value of the node """ - def __init__(self, name, update_func=None, dependencies=[], init_state=0): self.name = name self.update_func = update_func @@ -55,8 +71,7 @@ class Node: self.dependencies.append(new_dependency) def update(self, neigh_vals): - f = self.update_func - self.state = f(neigh_vals) + self.state = self.update_func(neigh_vals) def __repr__(self): return "{{node {}: {}}}".format(self.name, self.state) @@ -90,11 +105,11 @@ class AN: written to `_nodes`. """ - def __init__(self, nodes={}, update_mode=[], history_len=10): + def __init__(self, nodes={}, update_mode=[], history_len=100): self.nodes = nodes self.update_mode = update_mode self.up = 0 - self.history = collections.deque(maxlen=history_len) + self.history = deque(maxlen=history_len) # ################################################################ @@ -138,7 +153,7 @@ class AN: @history_len.setter def history_len(self, new_history_len): - new_history = collections.deque(new_history_len) + new_history = deque(maxlen=new_history_len) for i in range(min(new_history_len, self.history_len)): new_history.append(self.history[-(i+1)]) self.history = new_history @@ -154,28 +169,26 @@ class AN: def reset_state(self): """Set each node to its initial value, and the up to 0.""" - new_nodes = self._nodes.copy() + new_nodes = deepcopy(self._nodes) for node in new_nodes: node.reset_state() - self.history.append(self._nodes) self._nodes = new_nodes self.up = 0 def update(self): """Update the AN once.""" - new_nodes = copy.deepcopy(self._nodes) + new_nodes = deepcopy(self._nodes) for n in self.update_mode[self.up]: local_view = dict_filter(self.state, self._nodes[n].dependencies) new_nodes[n].update(local_view) - - self.history.append(self.nodes) + self.history.append(self._nodes) self._nodes = new_nodes self.up += 1 def unupdate(self): """Roll back one step of the AN, within limits of history_len.""" - self._nodes = copy.deepcopy(self.history.pop()) + self._nodes = self.history.pop() self.up -= 1 def run(self, steps): @@ -220,7 +233,7 @@ def is_block_seq(mode): def block_seq(mode): """ Checks that `mode` is block-sequential. If so, return it. - If not, raise a ValueError exception. Useful for functoinal programming. + If not, raise a ValueError exception. Useful for functional programming. """ if not is_block_seq(mode): raise ValueError() return mode @@ -228,12 +241,12 @@ def block_seq(mode): def periodic_mode(mode): """ The identity function, with another name. - Useful for functionnal programming. + Useful for functional programming. """ return mode # ################################################################ -# Trivial nodes +# Node construction def node_fixpoint(name, init_val=0): """ @@ -302,7 +315,7 @@ def cycle(signs, init_vals=None, update_mode=None): return AN(nodes, update_mode) # ################################################################ -# And-not symmetric networks +# And-not networks def land(b1, b2): """Like `and`, but a function (usable with `reduce`)""" @@ -311,7 +324,7 @@ def land(b1, b2): def and_not_update_func(dep_list): """ Return an update function for a node in an and_not network. - If dep_list is [1, -2, 3, -4], the node has positive neighbours 1, 3 + If dep_list is [1, -2, 3, -4], then the node has positive neighbours 1, 3 and negative neighbours 2, 4. """ def node_update_func(args): @@ -341,11 +354,12 @@ def and_not(dep_lists, init_vals, update_mode): """ nodes = [] for (dep_list, init_val, i) in zip(dep_lists[1:], init_vals[1:], count(1)): - update_func = ant_not_update_func(dep_list) + print("{} {} {}".format(dep_list, init_val, i)) + update_func = and_not_update_func(dep_list) deps = [abs(d) for d in dep_list] node = Node(i, update_func, deps, init_val) nodes.append(node) - return AN(nodes[1:], update_mode) + return AN(nodes, update_mode) # ################################################################ @@ -375,27 +389,3 @@ example_update = [ [], ] -# ################################################################ - -def button_press(event): - print("clicked at ({},{})".format(event.x, event.y)) - -def button_release(event): - print("Released at ({},{})".format(event.x, event.y)) - -def main(): - window = Tk() - window.title("SIMulateur de Réseau d'Automates") - - canvas = Canvas(window) - canvas.create_rectangle(128, 64, 32, 32, - outline="#ff0000", fill="#aaaaaa") - canvas.bind("<Button-1>", button_press) - canvas.bind("<ButtonRelease-1>", button_release) - canvas.pack(fill=BOTH, expand=1) - - window.mainloop() - -if __name__ == '__main__': - main() -