Franck Pommereau

added a plugin to provide places boundedness

1 +"""A plugin to implement boundedness on places.
2 +
3 +When this plugin is loaded, `Place` constructor accepts a parameter
4 +`bound` that allows to specify the minimal and maximal number of
5 +tokens that the place is allowed to carry. (Repeated tokens count as
6 +many times as they are repeated.) Exception `ConstraintError` is
7 +raised whenever an operation (like a transition firing) leads to
8 +violate any of a place bound. (But note that direct modifications of a
9 +`Place.tokens` are not checked.)
10 +
11 +>>> import snakes.plugins
12 +>>> snakes.plugins.load('bound', 'snakes.nets', 'nets')
13 +<module ...>
14 +
15 +If parameter `bound` is given as a non-negative integer, this is the
16 +upper bound of the place (and its lower bound is zero).
17 +
18 +>>> from nets import *
19 +>>> n = PetriNet('N')
20 +>>> p = Place('p', [dot], bound=3)
21 +>>> n.add_place(p)
22 +>>> put = Transition('put')
23 +>>> n.add_transition(put)
24 +>>> n.add_output('p', 'put', Value(dot))
25 +>>> p.tokens
26 +MultiSet([dot])
27 +>>> put.fire(Substitution())
28 +>>> p.tokens
29 +MultiSet([dot, dot])
30 +>>> put.fire(Substitution())
31 +>>> p.tokens
32 +MultiSet([dot, dot, dot])
33 +>>> put.fire(Substitution())
34 +Traceback (most recent call last):
35 + ...
36 +ConstraintError: upper bound of place 'p' reached
37 +
38 +If `bound` is given as a pair `(n, None)` where `n` is a non-negative
39 +integer, then `n` is the lower bound of the place (and it has no upper
40 +bound).
41 +
42 +>>> n = PetriNet('N')
43 +>>> p = Place('p', [dot, dot], bound=(1, None))
44 +>>> n.add_place(p)
45 +>>> put = Transition('put')
46 +>>> n.add_transition(put)
47 +>>> n.add_output('p', 'put', Value(dot))
48 +>>> get = Transition('get')
49 +>>> n.add_transition(get)
50 +>>> n.add_input('p', 'get', Value(dot))
51 +>>> p.tokens
52 +MultiSet([dot, dot])
53 +>>> get.fire(Substitution())
54 +>>> p.tokens
55 +MultiSet([dot])
56 +>>> get.fire(Substitution())
57 +Traceback (most recent call last):
58 + ...
59 +ConstraintError: lower bound of place 'p' reached
60 +>>> for i in range(100) : # no upper bound
61 +... put.fire(Substitution())
62 +
63 +If `bound` is given as a pair of non-negative integers `(n,m)` such
64 +that `n <= m` then `n` is the lower bound of the place and `m` its
65 +upper bound.
66 +
67 +>>> n = PetriNet('N')
68 +>>> p = Place('p', [dot, dot], bound=(1, 3))
69 +>>> n.add_place(p)
70 +>>> put = Transition('put')
71 +>>> n.add_transition(put)
72 +>>> n.add_output('p', 'put', Value(dot))
73 +>>> get = Transition('get')
74 +>>> n.add_transition(get)
75 +>>> n.add_input('p', 'get', Value(dot))
76 +>>> p.tokens
77 +MultiSet([dot, dot])
78 +>>> put.fire(Substitution())
79 +>>> p.tokens
80 +MultiSet([dot, dot, dot])
81 +>>> put.fire(Substitution())
82 +Traceback (most recent call last):
83 + ...
84 +ConstraintError: upper bound of place 'p' reached
85 +>>> get.fire(Substitution())
86 +>>> p.tokens
87 +MultiSet([dot, dot])
88 +>>> get.fire(Substitution())
89 +>>> p.tokens
90 +MultiSet([dot])
91 +>>> get.fire(Substitution())
92 +Traceback (most recent call last):
93 + ...
94 +ConstraintError: lower bound of place 'p' reached
95 +
96 +Any other value for bound is refused raising a `ValueError`.
97 +
98 +"""
99 +
100 +from snakes.data import iterate
101 +from snakes import ConstraintError
102 +import snakes.plugins
103 +
104 +@snakes.plugins.plugin("snakes.nets")
105 +def extend (module) :
106 + "Extends `module`"
107 + class Place (module.Place) :
108 + "Extend places with boundedness"
109 + def __init__ (self, name, tokens, check=None, **args) :
110 + """Add new keyword argument `bound`
111 +
112 + @param args: plugin options
113 + @keyword bound: the place boundaries,
114 + @type bound: `int` or `(int, None)` or `(int, int)`
115 + """
116 + bound = args.pop("bound", (0, None))
117 + if isinstance(bound, int) and bound >= 0 :
118 + self._bound_min, self._bound_max = 0, bound
119 + elif (isinstance(bound, tuple) and len(bound) == 2
120 + and isinstance(bound[0], int) and bound[0] >= 0
121 + and ((isinstance(bound[1], int) and bound[1] >= bound[0])
122 + or bound[1] is None)) :
123 + self._bound_min, self._bound_max = bound
124 + else :
125 + raise ValueError("invalid value for parameter 'bound'")
126 + module.Place.__init__(self, name, tokens, check, **args)
127 + def add (self, tokens) :
128 + """Add tokens to the place.
129 +
130 + @param tokens: a collection of tokens to be added to the
131 + place, note that `str` are not considered as iterable and
132 + used a a single value instead of as a collection
133 + @type tokens: `collection`
134 + """
135 + if (self._bound_max is not None
136 + and len(self.tokens) + len(list(iterate(tokens))) > self._bound_max) :
137 + raise ConstraintError("upper bound of place %r reached" % self.name)
138 + module.Place.add(self, tokens)
139 + def remove (self, tokens) :
140 + """Remove tokens from the place.
141 +
142 + @param tokens: a collection of tokens to be removed from the
143 + place, note that `str` are not considered as iterable and
144 + used a a single value instead of as a collection
145 + @type tokens: `collection`
146 + """
147 + if len(self.tokens) - len(list(iterate(tokens))) < self._bound_min :
148 + raise ConstraintError("lower bound of place %r reached" % self.name)
149 + module.Place.remove(self, tokens)
150 + def reset (self, tokens) :
151 + """Replace the marking with `tokens`.
152 +
153 + @param tokens: a collection of tokens to be removed from the
154 + place, note that `str` are not considered as iterable and
155 + used a a single value instead of as a collection
156 + @type tokens: `collection`
157 + """
158 + count = len(list(iterate(tokens)))
159 + bmax = count if self._bound_max is None else self._bound_max
160 + if not (self._bound_min <= count <= bmax) :
161 + raise ConstraintError("not within bounds of place %r" % self.name)
162 + module.Place.reset(self, tokens)
163 + return Place