Showing
1 changed file
with
163 additions
and
0 deletions
snakes/plugins/bound.py
0 → 100644
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 |
-
Please register or login to post a comment