Skip to content
Snippets Groups Projects
Commit d0da03de authored by Guilhem Gamard's avatar Guilhem Gamard
Browse files

Fixed nasty bug in AN.update()

parent 14d7e75d
No related branches found
No related tags found
No related merge requests found
/#!/usr/bin/env python3
#!/usr/bin/env python3
import collections
import copy
from functools import reduce
from itertools import count
import tkinter as TK
# ################################################################
# General utilities
def dict_filter(dictionary, keys):
"""
......@@ -12,7 +14,17 @@ def dict_filter(dictionary, keys):
"""
return dict((key, dictionary[key]) for key in keys)
def integers(n):
"""
Return the list of integers from 0 (included) to n-1 (included).
"""
return list(range(n))
def flatten(l):
return [item for sublist in l for item in sublist]
# ################################################################
# Automata network themselves
class Node:
"""
......@@ -37,13 +49,17 @@ class Node:
self.reset_state() # Sets self.state
def reset_state(self):
self.state = self.init_state.copy()
self.state = self.init_state
def add_dependency(self, new_dependency):
self.dependencies.append(new_dependency)
def update(self, neigh_vals):
self.state = self.update_func(neigh_vals)
f = self.update_func
self.state = f(neigh_vals)
def __repr__(self):
return "{{node {}: {}}}".format(self.name, self.state)
class AN:
"""
......@@ -59,11 +75,11 @@ class AN:
Other fields:
up -- Stands for Update Pointer.
It is an index in update_mode thant “points” to the next update
It is an index in update_mode that “points” to the next update
to perform. Note that foo.up can be publicly read and written.
A modulo len(update_mode) is implicitly applied, so it is safe
to do, e.g., foo.up +=1 on an update.
history -- A circular buffer of deep copies of the previous values of _node
history -- A circular buffer of deep copies of the previous values of _nodes
(see below).
It can be publicly accessed. Please do not alter the history
by yourself (Stalin did nothing right).
......@@ -85,13 +101,18 @@ class AN:
@property
def nodes(self):
"""Return the set of all nodes."""
return self._nodes.items()
return self._nodes.values()
@nodes.setter
def nodes(self, new_nodes):
self._nodes = {}
for node in new_nodes: self.add_node(node)
@property
def state(self):
"""Return a dictionary mapping node names to node states."""
return dict((name, self._nodes[name].state) for name in self._nodes.keys())
def add_node(self, node):
"""Adds a new node to the AN."""
self._nodes[node.name] = node
......@@ -122,11 +143,18 @@ class AN:
new_history.append(self.history[-(i+1)])
self.history = new_history
def __repr__(self):
result = "{AN \n"
for node in self.nodes:
result += " " + str(node) + "\n"
result += "}"
return result
# ################################################################
def reset_state(self):
"""Set each node to its initial value, and the up to 0."""
new_nodes = self._nodes.deep_copy()
new_nodes = self._nodes.copy()
for node in new_nodes:
node.reset_state()
......@@ -136,9 +164,9 @@ class AN:
def update(self):
"""Update the AN once."""
new_nodes = self._nodes.deep_copy()
new_nodes = copy.deepcopy(self._nodes)
for n in self.update_mode[self.up]:
local_view = dict_filter(self.nodes, self.nodes[n].dependencies)
local_view = dict_filter(self.state, self._nodes[n].dependencies)
new_nodes[n].update(local_view)
self.history.append(self.nodes)
......@@ -147,7 +175,7 @@ class AN:
def unupdate(self):
"""Roll back one step of the AN, within limits of history_len."""
self._nodes = self.history.pop()
self._nodes = copy.deepcopy(self.history.pop())
self.up -= 1
def run(self, steps):
......@@ -160,6 +188,112 @@ class AN:
self.update()
# ################################################################
# Update modes
def parallel_mode(node_names):
"""Return a parallel update mode."""
return [node_names]
def local_clocks(period, deltas):
"""
Return a local clocks update mode.
period -- The global period update.
deltas -- A dictionary mapping node names to phases.
"""
result = [] * global_periods
for k in deltas.keys():
result[deltas[k]].append(k)
return result
def is_block_seq(mode):
"""
Return whether an update mode is block sequential
(no node is updated twice).
"""
already_seen = set()
for node_name in flatten(mode):
if node_name in already_seen: return False
else: already_seen.add(name)
return True
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 is_block_seq(mode): raise ValueError()
return mode
def periodic_mode(mode):
"""
The identity function, with another name.
Useful for functionnal programming.
"""
return mode
# ################################################################
# Trivial nodes
def node_fixpoint(name, init_val=0):
def identity(args):
for k in args.keys():
return args[k]
return Node(name, identity, [name], init_val)
def node_constant(name, value=0, init_val=value):
def constant(args):
nonlocal value
return value
return Node(name, constant, [], init_val)
# ################################################################
# Cycle ANs
def cycle_update_func(sign=True):
"""
Return an update function for a node in a cycle.
All update functions assume only one neighbour.
If sign=True, the returned update function is identity.
If sign=False, the returned update function is negation.
"""
def positive(neighs):
for k in neighs.keys():
return neighs[k]
def negative(neighs):
for k in neighs.keys():
return not neighs[k]
if sign: return positive
else: return negative
def cycle(signs, init_vals=None, update_mode=None):
"""
Return a cyclic AN.
Node names are 0,…,n-1, with n=len(signs). Node i depends on node i-1 mod n.
signs -- signs[i] is True if i depends positively on i-1, False if
negatively
init_vals -- list of initial values (booleans); defaults to all True
update_mode -- a periodic update mode; defaults to parallel
"""
num_nodes = len(signs)
node_names = integers(num_nodes)
if not init_vals:
init_vals = [True]*num_nodes
if not update_mode:
update_mode = parallel_mode(node_names)
nodes = []
for (sign, init_val, i) in zip(signs, init_vals, count()):
update_func = cycle_update_func(sign)
node = Node(i, update_func, [(i-1)%num_nodes], init_val)
nodes.append(node)
return AN(nodes, update_mode)
# ################################################################
# And-not symmetric networks
def land(b1, b2):
"""Like `and`, but a function (usable with `reduce`)"""
......@@ -182,19 +316,19 @@ def and_not_update_func(dep_list):
def and_not(dep_lists, init_vals, update_mode):
"""
Builds an and-not network.
Return an and-not network.
Nodes are named 1, …, N where N=len(dep_lists)==len(init_vals).
dep_lists[0] and init_vals[0] are ignored.
dep_lists[i] is the list of neighbours of i (so a list of integers).
dep_lists -- dep_lists[0] is ignored; dep_lists[i] is the list of
neighbours of i (so a list of integers).
A negative neighbour means a negated neighbour.
For example if dep_list[1] contains 4, then 1 depends positively on 4;
if dep_list[1] contains -4, then 1 depends negatively on 4.
init_vals is a list of booleans, so that init_vals[i] is the initial
value of node i.
update_mode is a periodic update mode, i.e. a list of sets of integers.
init_vals -- init_vals[0] is ignored; init_vals[i] is the initial value of
node i
update_mode -- periodic update mode, i.e. a list of sets of integers
"""
nodes = []
for (dep_list, init_val, i) in zip(dep_lists[1:], init_vals[1:], count(1)):
......@@ -234,12 +368,6 @@ example_update = [
# ################################################################
def parallel_mode(AN):
"""Return a parallel update mode for AN."""
return [[node.name for node in AN.nodes]]
# ################################################################
def button_press(event):
print("clicked at ({},{})".format(event.x, event.y))
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment