Franck Pommereau

doc update + synchro fixes

...@@ -895,6 +895,25 @@ class Substitution (object) : ...@@ -895,6 +895,25 @@ class Substitution (object) :
895 for var in other : 895 for var in other :
896 res._dict[var] = self(other(var)) 896 res._dict[var] = self(other(var))
897 return res 897 return res
898 + def restrict (self, domain) :
899 + """Restrict the substitution to `domain`, ie remove all
900 + elements that are not in `domain`. Note that `domain` may
901 + include names that are not in the substitution, they are
902 + simply ignored.
903 +
904 + >>> s = Substitution(a=1, b=2, c=3, d=4).restrict(['a', 'b', 'z'])
905 + >>> list(sorted(s.domain()))
906 + ['a', 'b']
907 +
908 + @param domain: the new domain as a set/list/... of names
909 + @type domain: `iterable`
910 + @return: the restricted substitution
911 + @rtype: `Substitution`
912 + """
913 + result = self.copy()
914 + for name in result.domain() - set(domain) :
915 + result._dict.pop(name, None)
916 + return result
898 917
899 class Symbol (object) : 918 class Symbol (object) :
900 """A symbol that may be used as a constant 919 """A symbol that may be used as a constant
......
This diff is collapsed. Click to expand it.
1 -"""Draw Petri nets using PyGraphViz 1 +"""Adds methods to draw `PetriNet` and `StateGraph` instances using
2 +GraphViz.
2 3
3 - * adds a method `draw` to `PetriNet` and `StateGraph` that creates 4 +For example, let's first define a Petri net:
4 - a drawing of the object in a file.
5 5
6 >>> import snakes.plugins 6 >>> import snakes.plugins
7 >>> snakes.plugins.load('gv', 'snakes.nets', 'nets') 7 >>> snakes.plugins.load('gv', 'snakes.nets', 'nets')
...@@ -16,18 +16,35 @@ ...@@ -16,18 +16,35 @@
16 >>> n.add_output('p11', 't10', Expression('x+1')) 16 >>> n.add_output('p11', 't10', Expression('x+1'))
17 >>> n.add_input('p11', 't01', Variable('y')) 17 >>> n.add_input('p11', 't01', Variable('y'))
18 >>> n.add_output('p00', 't01', Expression('y-1')) 18 >>> n.add_output('p00', 't01', Expression('y-1'))
19 +
20 +Thanks to plugin `gv`, we can draw it using the various engines of
21 +GraphViz; we can also draw the state graph:
22 +
19 >>> for engine in ('neato', 'dot', 'circo', 'twopi', 'fdp') : 23 >>> for engine in ('neato', 'dot', 'circo', 'twopi', 'fdp') :
20 ... n.draw(',test-gv-%s.png' % engine, engine=engine) 24 ... n.draw(',test-gv-%s.png' % engine, engine=engine)
21 >>> s = StateGraph(n) 25 >>> s = StateGraph(n)
22 >>> s.build() 26 >>> s.build()
23 >>> s.draw(',test-gv-graph.png') 27 >>> s.draw(',test-gv-graph.png')
24 ->>> for node in sorted(n.node(), key=str) : 28 +
29 +The plugin also allows to layout the nodes without drawing the net
30 +(this is only available for `PetriNet`, not for `StateGraph`). We
31 +first move every node to position `(-100, -100)`; then, we layout the
32 +net; finally, we check that every node has indeed been moved away from
33 +where we had put it:
34 +
35 +>>> for node in n.node() :
25 ... node.pos.moveto(-100, -100) 36 ... node.pos.moveto(-100, -100)
37 +>>> all(node.pos.x == node.pos.y == -100 for node in n.node())
38 +True
26 >>> n.layout() 39 >>> n.layout()
27 ->>> any(node.pos == (-100, -100) for node in sorted(n.node(), key=str)) 40 +>>> any(node.pos.x == node.pos.y == -100 for node in n.node())
28 False 41 False
29 42
30 -@todo: revise documentation 43 +@note: setting nodes position has no influence on how a net is drawn:
44 + GraphViz will redo the layout in any case. Method `layout` is here
45 + just in the case you would need a layout of your nets.
46 +@note: this plugin depens on plugins `pos` and `clusters` (that are
47 + automatically loaded)
31 """ 48 """
32 49
33 import os, os.path, subprocess, collections 50 import os, os.path, subprocess, collections
...@@ -35,6 +52,7 @@ import snakes.plugins ...@@ -35,6 +52,7 @@ import snakes.plugins
35 from snakes.plugins.clusters import Cluster 52 from snakes.plugins.clusters import Cluster
36 from snakes.compat import * 53 from snakes.compat import *
37 54
55 +# apidoc skip
38 class Graph (Cluster) : 56 class Graph (Cluster) :
39 def __init__ (self, attr) : 57 def __init__ (self, attr) :
40 Cluster.__init__(self) 58 Cluster.__init__(self)
...@@ -150,37 +168,56 @@ class Graph (Cluster) : ...@@ -150,37 +168,56 @@ class Graph (Cluster) :
150 "snakes.plugins.pos"]) 168 "snakes.plugins.pos"])
151 def extend (module) : 169 def extend (module) :
152 class PetriNet (module.PetriNet) : 170 class PetriNet (module.PetriNet) :
153 - "An extension with a method `draw`" 171 + "An extension with methods `draw` and `layout`"
154 - def draw (self, filename=None, engine="dot", debug=False, 172 + def draw (self, filename, engine="dot", debug=False,
155 graph_attr=None, cluster_attr=None, 173 graph_attr=None, cluster_attr=None,
156 place_attr=None, trans_attr=None, arc_attr=None) : 174 place_attr=None, trans_attr=None, arc_attr=None) :
157 - """ 175 + """Draw the Petri net to a picture file. How the net is
158 - @param filename: the name of the image file to create or 176 + rendered can be controlled using the arguments `..._attr`.
159 - `None` if only the computed graph is needed 177 + For instance, to draw place in red with names in
160 - @type filename: `None` or `str` 178 + uppercase, and hide True guards, we can proceed as
161 - @param engine: the layout engine to use: 'dot' (default), 179 + follows:
162 - 'neato', 'circo', 'twopi' or 'fdp' 180 +
181 + >>> import snakes.plugins
182 + >>> snakes.plugins.load('gv', 'snakes.nets', 'nets')
183 + <module ...>
184 + >>> from nets import *
185 + >>> n = PetriNet('N')
186 + >>> n.add_place(Place('p'))
187 + >>> n.add_transition(Transition('t'))
188 + >>> n.add_input('p', 't', Value(dot))
189 + >>> def draw_place (place, attr) :
190 + ... attr['label'] = place.name.upper()
191 + ... attr['color'] = '#FF0000'
192 + >>> def draw_transition (trans, attr) :
193 + ... if str(trans.guard) == 'True' :
194 + ... attr['label'] = trans.name
195 + ... else :
196 + ... attr['label'] = '%s\\n%s' % (trans.name, trans.guard)
197 + >>> n.draw(',net-with-colors.png',
198 + ... place_attr=draw_place, trans_attr=draw_transition)
199 +
200 + @param filename: the name of the image file to create
201 + @type filename: `str`
202 + @param engine: the layout engine to use: `'dot'` (default),
203 + `'neato'`, `'circo'`, `'twopi'` or `'fdp'`
163 @type engine: `str` 204 @type engine: `str`
164 @param place_attr: a function to format places, it will be 205 @param place_attr: a function to format places, it will be
165 called with the place and its attributes dict as 206 called with the place and its attributes dict as
166 parameters 207 parameters
167 - @type place_attr: `function(Place,dict)->None` 208 + @type place_attr: `callable`
168 @param trans_attr: a function to format transitions, it 209 @param trans_attr: a function to format transitions, it
169 will be called with the transition and its attributes 210 will be called with the transition and its attributes
170 dict as parameters 211 dict as parameters
171 - @type trans_attr: `function(Transition,dict)->None` 212 + @type trans_attr: `callable`
172 @param arc_attr: a function to format arcs, it will be 213 @param arc_attr: a function to format arcs, it will be
173 called with the label and its attributes dict as 214 called with the label and its attributes dict as
174 parameters 215 parameters
175 - @type arc_attr: `function(ArcAnnotation,dict)->None` 216 + @type arc_attr: `callable`
176 @param cluster_attr: a function to format clusters of 217 @param cluster_attr: a function to format clusters of
177 nodes, it will be called with the cluster and its 218 nodes, it will be called with the cluster and its
178 attributes dict as parameters 219 attributes dict as parameters
179 - @type cluster_attr: 220 + @type cluster_attr: `callable`
180 - `function(snakes.plugins.clusters.Cluster,dict)->None`
181 - @return: `None` if `filename` is not `None`, the computed
182 - graph otherwise
183 - @rtype: `None` or `pygraphviz.AGraph`
184 """ 221 """
185 nodemap = dict((node.name, "node_%s" % num) 222 nodemap = dict((node.name, "node_%s" % num)
186 for num, node in enumerate(self.node())) 223 for num, node in enumerate(self.node()))
...@@ -236,17 +273,51 @@ def extend (module) : ...@@ -236,17 +273,51 @@ def extend (module) :
236 def layout (self, xscale=1.0, yscale=1.0, engine="dot", 273 def layout (self, xscale=1.0, yscale=1.0, engine="dot",
237 debug=False, graph_attr=None, cluster_attr=None, 274 debug=False, graph_attr=None, cluster_attr=None,
238 place_attr=None, trans_attr=None, arc_attr=None) : 275 place_attr=None, trans_attr=None, arc_attr=None) :
276 + """Layout the nodes of the Petri net by calling GraphViz
277 + and reading back the picture it creates. The effect is to
278 + change attributes `pos` (see plugin `pos`) for every node
279 + according to the positions calculated by GraphViz.
280 +
281 + @param xscale: how much the image is scaled in the
282 + horizontal axis after GraphViz has done the layout
283 + @type xscale: `float`
284 + @param yscale: how much the image is scaled in the
285 + vertical axis after GraphViz has done the layout
286 + @type yscale: `float`
287 + @param filename: the name of the image file to create
288 + @type filename: `str`
289 + @param engine: the layout engine to use: `'dot'` (default),
290 + `'neato'`, `'circo'`, `'twopi'` or `'fdp'`
291 + @type engine: `str`
292 + @param place_attr: a function to format places, it will be
293 + called with the place and its attributes dict as
294 + parameters
295 + @type place_attr: `callable`
296 + @param trans_attr: a function to format transitions, it
297 + will be called with the transition and its attributes
298 + dict as parameters
299 + @type trans_attr: `callable`
300 + @param arc_attr: a function to format arcs, it will be
301 + called with the label and its attributes dict as
302 + parameters
303 + @type arc_attr: `callable`
304 + @param cluster_attr: a function to format clusters of
305 + nodes, it will be called with the cluster and its
306 + attributes dict as parameters
307 + @type cluster_attr: `callable`
308 + """
239 g = self.draw(None, engine, debug, graph_attr, cluster_attr, 309 g = self.draw(None, engine, debug, graph_attr, cluster_attr,
240 place_attr, trans_attr, arc_attr) 310 place_attr, trans_attr, arc_attr)
241 node = dict((v, k) for k, v in g.nodemap.items()) 311 node = dict((v, k) for k, v in g.nodemap.items())
242 for n, x, y in g.layout(engine, debug) : 312 for n, x, y in g.layout(engine, debug) :
243 self.node(node[n]).pos.moveto(x*xscale, y*yscale) 313 self.node(node[n]).pos.moveto(x*xscale, y*yscale)
244 -
245 class StateGraph (module.StateGraph) : 314 class StateGraph (module.StateGraph) :
246 "An extension with a method `draw`" 315 "An extension with a method `draw`"
247 - def draw (self, filename=None, engine="dot", debug=False, 316 + def draw (self, filename, engine="dot", debug=False,
248 node_attr=None, edge_attr=None, graph_attr=None) : 317 node_attr=None, edge_attr=None, graph_attr=None) :
249 - """@param filename: the name of the image file to create or 318 + """Draw the state graph to a picture file.
319 +
320 + @param filename: the name of the image file to create or
250 `None` if only the computed graph is needed 321 `None` if only the computed graph is needed
251 @type filename: `None` or `str` 322 @type filename: `None` or `str`
252 @param engine: the layout engine to use: 'dot' (default), 323 @param engine: the layout engine to use: 'dot' (default),
...@@ -255,19 +326,16 @@ def extend (module) : ...@@ -255,19 +326,16 @@ def extend (module) :
255 @param node_attr: a function to format nodes, it will be 326 @param node_attr: a function to format nodes, it will be
256 called with the state number, the `StateGraph` object 327 called with the state number, the `StateGraph` object
257 and attributes dict as parameters 328 and attributes dict as parameters
258 - @type node_attr: `function(int,StateGraph,dict)->None` 329 + @type node_attr: `callable`
259 @param edge_attr: a function to format edges, it will be 330 @param edge_attr: a function to format edges, it will be
260 called with the transition, its mode and attributes 331 called with the transition, its mode and attributes
261 dict as parameters 332 dict as parameters
262 @type trans_attr: 333 @type trans_attr:
263 - `function(Transition,Substitution,dict)->None` 334 + `callable`
264 @param graph_attr: a function to format grapg, it will be 335 @param graph_attr: a function to format grapg, it will be
265 called with the state graphe and attributes dict as 336 called with the state graphe and attributes dict as
266 parameters 337 parameters
267 - @type graph_attr: `function(StateGraph,dict)->None` 338 + @type graph_attr: `callable`
268 - @return: `None` if `filename` is not `None`, the computed
269 - graph otherwise
270 - @rtype: `None` or `pygraphviz.AGraph`
271 """ 339 """
272 attr = dict(style="invis", 340 attr = dict(style="invis",
273 splines="true") 341 splines="true")
......
1 -"""A plugin to add labels to nodes and nets. 1 +"""A plugin to add labels to nodes and nets. Labels are names (valid
2 +Python identifiers) associated to arbitrary objects.
2 3
3 -@todo: revise (actually make) documentation 4 +>>> import snakes.plugins
5 +>>> snakes.plugins.load('labels', 'snakes.nets', 'nets')
6 +<module ...>
7 +>>> from nets import *
8 +>>> t = Transition('t')
9 +>>> t.label(foo='bar', spam=42)
10 +>>> t.label('foo')
11 +'bar'
12 +>>> t.label('spam')
13 +42
14 +
15 +Note that when nodes in a Petri net are merged, their labels are
16 +merged too, but in an arbitrary order. So, for example:
17 +
18 +>>> n = PetriNet('N')
19 +>>> n.add_place(Place('p1'))
20 +>>> n.place('p1').label(foo='bar', spam='ham')
21 +>>> n.add_place(Place('p2'))
22 +>>> n.place('p2').label(hello='world', spam='egg')
23 +>>> n.merge_places('p', ['p1', 'p2'])
24 +>>> n.place('p').label('hello')
25 +'world'
26 +>>> n.place('p').label('foo')
27 +'bar'
28 +>>> n.place('p').label('spam') in ['ham', 'egg']
29 +True
30 +
31 +In the latter statement, we cannot know whether the label will be one
32 +or the other value because merging has been done in an arbitrary
33 +order.
4 """ 34 """
5 35
6 from snakes.plugins import plugin, new_instance 36 from snakes.plugins import plugin, new_instance
...@@ -10,6 +40,22 @@ from snakes.pnml import Tree ...@@ -10,6 +40,22 @@ from snakes.pnml import Tree
10 def extend (module) : 40 def extend (module) :
11 class Transition (module.Transition) : 41 class Transition (module.Transition) :
12 def label (self, *get, **set) : 42 def label (self, *get, **set) :
43 + """Get and set labels for the transition. The labels given
44 + in `get` will be returned as a `tuple` and the labels
45 + assigned in `set` will be changed accordingly. If a label
46 + is given both in `get`and `set`, the returned value is
47 + that it had at the beginning of the call, ie, before it is
48 + set by the call.
49 +
50 + @param get: labels which values have to be returned
51 + @type get: `str`
52 + @param set: labels which values have to be changed
53 + @type set: `object`
54 + @return: the tuples of values corresponding to `get`
55 + @rtype: `tuple`
56 + @raise KeyError: when a label given in `get` has not been
57 + assigned previouly
58 + """
13 if not hasattr(self, "_labels") : 59 if not hasattr(self, "_labels") :
14 self._labels = {} 60 self._labels = {}
15 result = tuple(self._labels[g] for g in get) 61 result = tuple(self._labels[g] for g in get)
...@@ -21,10 +67,22 @@ def extend (module) : ...@@ -21,10 +67,22 @@ def extend (module) :
21 elif len(set) == 0 : 67 elif len(set) == 0 :
22 return self._labels.copy() 68 return self._labels.copy()
23 def has_label (self, name, *names) : 69 def has_label (self, name, *names) :
70 + """Check is a label has been assigned to the transition.
71 +
72 + @param name: the label to check
73 + @type name: `str`
74 + @param names: additional labels to check, if used, the
75 + return value is a `tuple` of `bool` instead of a
76 + single `bool`
77 + @return: a Boolean indicating of the checked labels are
78 + present or not in the transitions
79 + @rtype: `bool`
80 + """
24 if len(names) == 0 : 81 if len(names) == 0 :
25 return name in self._labels 82 return name in self._labels
26 else : 83 else :
27 return tuple(n in self._labels for n in (name,) + names) 84 return tuple(n in self._labels for n in (name,) + names)
85 + # apidoc stop
28 def copy (self, name=None, **options) : 86 def copy (self, name=None, **options) :
29 if not hasattr(self, "_labels") : 87 if not hasattr(self, "_labels") :
30 self._labels = {} 88 self._labels = {}
...@@ -79,6 +137,7 @@ def extend (module) : ...@@ -79,6 +137,7 @@ def extend (module) :
79 return t 137 return t
80 class Place (module.Place) : 138 class Place (module.Place) :
81 def label (self, *get, **set) : 139 def label (self, *get, **set) :
140 + "See documentation for `Transition.label` above"
82 if not hasattr(self, "_labels") : 141 if not hasattr(self, "_labels") :
83 self._labels = {} 142 self._labels = {}
84 result = tuple(self._labels[g] for g in get) 143 result = tuple(self._labels[g] for g in get)
...@@ -90,10 +149,12 @@ def extend (module) : ...@@ -90,10 +149,12 @@ def extend (module) :
90 elif len(set) == 0 : 149 elif len(set) == 0 :
91 return self._labels.copy() 150 return self._labels.copy()
92 def has_label (self, name, *names) : 151 def has_label (self, name, *names) :
152 + "See documentation for `Transition.has_label` above"
93 if len(names) == 0 : 153 if len(names) == 0 :
94 return name in self._labels 154 return name in self._labels
95 else : 155 else :
96 return tuple(n in self._labels for n in (name,) + names) 156 return tuple(n in self._labels for n in (name,) + names)
157 + # apidoc stop
97 def copy (self, name=None, **options) : 158 def copy (self, name=None, **options) :
98 if not hasattr(self, "_labels") : 159 if not hasattr(self, "_labels") :
99 self._labels = {} 160 self._labels = {}
...@@ -152,6 +213,7 @@ def extend (module) : ...@@ -152,6 +213,7 @@ def extend (module) :
152 return p 213 return p
153 class PetriNet (module.PetriNet) : 214 class PetriNet (module.PetriNet) :
154 def label (self, *get, **set) : 215 def label (self, *get, **set) :
216 + "See documentation for `Transition.label` above"
155 if not hasattr(self, "_labels") : 217 if not hasattr(self, "_labels") :
156 self._labels = {} 218 self._labels = {}
157 result = tuple(self._labels[g] for g in get) 219 result = tuple(self._labels[g] for g in get)
...@@ -163,10 +225,12 @@ def extend (module) : ...@@ -163,10 +225,12 @@ def extend (module) :
163 elif len(set) == 0 : 225 elif len(set) == 0 :
164 return self._labels.copy() 226 return self._labels.copy()
165 def has_label (self, name, *names) : 227 def has_label (self, name, *names) :
228 + "See documentation for `Transition.has_label` above"
166 if len(names) == 0 : 229 if len(names) == 0 :
167 return name in self._labels 230 return name in self._labels
168 else : 231 else :
169 return tuple(n in self._labels for n in (name,) + names) 232 return tuple(n in self._labels for n in (name,) + names)
233 + # apidoc stop
170 def copy (self, name=None, **options) : 234 def copy (self, name=None, **options) :
171 if not hasattr(self, "_labels") : 235 if not hasattr(self, "_labels") :
172 self._labels = {} 236 self._labels = {}
......
1 -"""A plugin to compose nets. 1 +# -*- encoding: latin-1
2 +"""A plugin to compose nets _à la_ Petri Box Calculus.
2 3
3 -The compositions are based on place status and automatically merge 4 +@note: this plugin depends on plugins `clusters` and `status` that are
4 -some nodes (buffers and variables, tick transitions). 5 + automatically loaded
6 +"""
7 +
8 +"""
9 +## Control-flow and buffers places ##
10 +
11 +When this module is used, places are equipped with statuses (see also
12 +plugin `status` that provides this service). We distinguish in
13 +particular:
14 +
15 + * _entry_ places marked at the initial state of the net
16 + * _exit_ places marked at a final state of the net
17 + * _internal_ places marked during the execution
18 + * all together, these places form the _control-flow places_ and can
19 + be marked only by black tokens (ie, they are typed `tBlackToken`
20 + and thus can hold only `dot` values)
21 + * _buffer_ places that may hold data of any type, each buffer place
22 + is given a name that is the name of the buffer modelled by the
23 + place
24 +
25 +Plugin `ops` exports these statuses as Python objects:[^1] `entry`,
26 +`internal` and `exit` are instances of class `Status` while `buffer`
27 +is a function that returns an instance of `Buffer` (a subclass of
28 +`Status`) when called with a name as its parameter.
29 +
30 +[^1]: All these objects are actually defined in plugin `status`
31 +
32 +Let's define a net with one entry place, one exit place and two buffer
33 +places. Note how we use keyword argument `status` to the place
34 +constructor to define the status of the created place:
5 35
6 >>> import snakes.plugins 36 >>> import snakes.plugins
7 >>> snakes.plugins.load('ops', 'snakes.nets', 'nets') 37 >>> snakes.plugins.load('ops', 'snakes.nets', 'nets')
8 <module ...> 38 <module ...>
9 >>> from nets import * 39 >>> from nets import *
10 ->>> from snakes.plugins.status import entry, internal, exit, buffer
11 >>> basic = PetriNet('basic') 40 >>> basic = PetriNet('basic')
12 >>> basic.add_place(Place('e', status=entry)) 41 >>> basic.add_place(Place('e', status=entry))
13 >>> basic.add_place(Place('x', status=exit)) 42 >>> basic.add_place(Place('x', status=exit))
14 >>> basic.add_transition(Transition('t')) 43 >>> basic.add_transition(Transition('t'))
15 ->>> basic.add_input('e', 't', Value(1)) 44 +>>> basic.add_input('e', 't', Value(dot))
16 ->>> basic.add_output('x', 't', Value(2)) 45 +>>> basic.add_output('x', 't', Value(dot))
17 ->>> basic.add_place(Place('b', [1], status=buffer('buf'))) 46 +>>> basic.add_place(Place('b1', [1], status=buffer('egg')))
47 +>>> basic.add_place(Place('b2', [2], status=buffer('spam')))
48 +>>> basic.add_input('b1', 't', Variable('x'))
49 +>>> basic.add_output('b2', 't', Expression('x+1'))
50 +
51 +The simplest operation is to change buffer names, let's do it on a
52 +copy of our net. This operation is called `hide` because it is
53 +basically used to hide a buffer:
18 54
19 >>> n = basic.copy() 55 >>> n = basic.copy()
20 ->>> n.hide(entry) 56 +>>> n.node('b1').status
21 ->>> n.node('e').status 57 +Buffer('buffer','egg')
58 +>>> n.hide(buffer('egg'))
59 +>>> n.node('b1').status
22 Status(None) 60 Status(None)
23 ->>> n.hide(buffer('buf'), buffer(None))
24 ->>> n.node('b').status
25 -Buffer('buffer')
26 61
27 ->>> n = basic / 'buf' 62 +As we can see, place `b1` now has a dummy status. But `hide` can
28 ->>> n.node('[b/buf]').status 63 +accept a second argument and allows to rename a buffer:
64 +
65 +>>> n.node('b2').status
66 +Buffer('buffer','spam')
67 +>>> n.hide(buffer('spam'), buffer('ham'))
68 +>>> n.node('b2').status
69 +Buffer('buffer','ham')
70 +
71 +A slighlty different way for hiding buffers is to use operator `/`,
72 +which actually constructs a new net, changing the names of every node
73 +in the original net to `'[.../egg]'`:
74 +
75 +>>> n = basic / 'egg'
76 +>>> n.node('[b1/egg]').status
29 Buffer('buffer') 77 Buffer('buffer')
30 78
79 +As we can see, a buffer hidden using `/` still has a buffer status but
80 +with no name associated. Such an anonymous buffer is treated in a
81 +special way as we'll see later on.
82 +
83 +The systematic renaming of nodes is something we should get used to
84 +before to continue. When one or two nets are constructed through an
85 +operation, the nodes of the operand nets are combined in one way or
86 +another. For an arbitrary operation `A % B`, the resulting net will be
87 +called `'[A.name%B.name]'` (if `A` and `B` are nets). Whenever a node
88 +`a` from `A` is combined with a node `b` from `B`, the resulting node
89 +will be called `'[a%b]'`. If only one node is copied in the resulting
90 +net, it will be called `'[a%]'` or `'[%b]'` depending on wher it comes
91 +from. This systematic renaming allows to ensure that even if `A` and
92 +`B` have nodes with the same names, there will be no name clash in the
93 +result, while, at the same time allowing users to predict the new name
94 +of a node.
95 +
96 +## Control-flow compositions ##
97 +
98 +Using control-flow places, it becomes possible to build nets by
99 +composing smaller nets through control flow operations. Let's start
100 +with the _sequential composition_ `A & B`, basically, its combines the
101 +exit places of net `A` with the entry places of net `B`, ensuring thus
102 +that the resulting net behaves like if we execute `A` followed by `B`.
103 +In the example below, we use method `status` of a net to get the
104 +places with a given status:
105 +
31 >>> n = basic & basic 106 >>> n = basic & basic
32 >>> n.status(internal) 107 >>> n.status(internal)
33 ('[x&e]',) 108 ('[x&e]',)
34 >>> n.place('[x&e]').pre 109 >>> n.place('[x&e]').pre
35 -{'[t&]': Value(2)} 110 +{'[t&]': Value(dot)}
36 >>> n.place('[x&e]').post 111 >>> n.place('[x&e]').post
37 -{'[&t]': Value(1)} 112 +{'[&t]': Value(dot)}
38 ->>> n.status(buffer('buf')) 113 +>>> n.status(buffer('egg'))
39 -('[b&b]',) 114 +('[b1&b1]',)
115 +>>> n.status(buffer('spam'))
116 +('[b2&b2]',)
117 +
118 +We can see that `n` now has one internal place that is the combination
119 +of the exit place of the left copy of `basic` with the entry place of
120 +the right copy of `basic`. (Hence its name: `'[x&e]'`.) We can see
121 +also how it is connected to the left/right copy of `t` as its
122 +input/output. Last, we can see that buffer places from the two copies
123 +of `basic` have been merged when they have had the same name. This
124 +merging won't occur for anonymous buffers. For example, hiding
125 +`'spam'` in the right net of the sequential composition will result in
126 +two buffer places, one still named `'spam'` that is the copy of `'b2'`
127 +from the left operand of `&`, another that is anonymous and is the
128 +copy of `'[b2/spam]'` (ie, `'b2'`after its status was hidden) from the
129 +right operand of `&`.
130 +
131 +>>> n = basic & (basic / 'spam')
132 +>>> n.status(buffer('spam'))
133 +('[b2&]',)
134 +>>> n.status(buffer(None))
135 +('[&[b2/spam]]',)
136 +
137 +The next operation is the _choice `A + B` that behave either as `A` or
138 +as `B`. This is obtained by comining the entry places of both nets one
139 +the one hand, and by combining their exit places on the other hand.
40 140
41 >>> n = basic + basic 141 >>> n = basic + basic
42 >>> n.status(entry) 142 >>> n.status(entry)
43 ('[e+e]',) 143 ('[e+e]',)
44 >>> list(sorted(n.place('[e+e]').post.items())) 144 >>> list(sorted(n.place('[e+e]').post.items()))
45 -[('[+t]', Value(1)), ('[t+]', Value(1))] 145 +[('[+t]', Value(dot)), ('[t+]', Value(dot))]
46 >>> n.status(exit) 146 >>> n.status(exit)
47 ('[x+x]',) 147 ('[x+x]',)
48 >>> list(sorted(n.place('[x+x]').pre.items())) 148 >>> list(sorted(n.place('[x+x]').pre.items()))
49 -[('[+t]', Value(2)), ('[t+]', Value(2))] 149 +[('[+t]', Value(dot)), ('[t+]', Value(dot))]
150 +
151 +Another operation is the _iteration_ `A * B` that behaves by executing
152 +`A` repeatedly (including no repetition) followed by one execution of
153 +`B`. This is obtained by combining the entry and exit places of `A`
154 +with the entry place of `B`.
50 155
51 >>> n = basic * basic 156 >>> n = basic * basic
52 >>> n.status(entry) 157 >>> n.status(entry)
53 ('[e,x*e]',) 158 ('[e,x*e]',)
54 >>> n.place('[e,x*e]').post 159 >>> n.place('[e,x*e]').post
55 -{'[t*]': Value(1), '[*t]': Value(1)} 160 +{'[t*]': Value(dot), '[*t]': Value(dot)}
56 >>> n.place('[e,x*e]').pre 161 >>> n.place('[e,x*e]').pre
57 -{'[t*]': Value(2)} 162 +{'[t*]': Value(dot)}
58 163
164 +Finally, there is the _parallel composition_ `A | B` that just
165 +executes both nets in parallel. But because of the merging of buffer
166 +places, they are able to communicate.
167 +
168 +>>> n = basic | basic
169 +>>> n.status(buffer('egg'))
170 +('[b1|b1]',)
171 +>>> n.status(buffer('spam'))
172 +('[b2|b2]',)
173 +
174 +>>> pass
59 >>> n1 = basic.copy() 175 >>> n1 = basic.copy()
60 >>> n1.declare('global x; x=1') 176 >>> n1.declare('global x; x=1')
61 >>> n2 = basic.copy() 177 >>> n2 = basic.copy()
...@@ -65,8 +181,6 @@ Buffer('buffer') ...@@ -65,8 +181,6 @@ Buffer('buffer')
65 (1, 2) 181 (1, 2)
66 >>> n._declare 182 >>> n._declare
67 ['global x; x=1'] 183 ['global x; x=1']
68 -
69 -@todo: revise documentation
70 """ 184 """
71 185
72 import snakes.plugins 186 import snakes.plugins
...@@ -113,13 +227,14 @@ def _glue (op, one, two) : ...@@ -113,13 +227,14 @@ def _glue (op, one, two) :
113 depends=["snakes.plugins.clusters", 227 depends=["snakes.plugins.clusters",
114 "snakes.plugins.status"]) 228 "snakes.plugins.status"])
115 def extend (module) : 229 def extend (module) :
116 - "Build the extended module" 230 + """Essentially, class `PetriNet` is extended to support the binary
231 + operations discussed above."""
117 class PetriNet (module.PetriNet) : 232 class PetriNet (module.PetriNet) :
118 def __or__ (self, other) : 233 def __or__ (self, other) :
119 - "Parallel" 234 + "Parallel composition"
120 return _glue("|", self, other) 235 return _glue("|", self, other)
121 def __and__ (self, other) : 236 def __and__ (self, other) :
122 - "Sequence" 237 + "Sequential composition"
123 result = _glue("&", self, other) 238 result = _glue("&", self, other)
124 remove = set() 239 remove = set()
125 for x, e in cross((self.status(exit), other.status(entry))) : 240 for x, e in cross((self.status(exit), other.status(entry))) :
...@@ -161,11 +276,13 @@ def extend (module) : ...@@ -161,11 +276,13 @@ def extend (module) :
161 result.remove_place(p) 276 result.remove_place(p)
162 return result 277 return result
163 def hide (self, old, new=None) : 278 def hide (self, old, new=None) :
279 + "Status hiding and renaming"
164 if new is None : 280 if new is None :
165 new = Status(None) 281 new = Status(None)
166 for node in self.status(old) : 282 for node in self.status(old) :
167 self.set_status(node, new) 283 self.set_status(node, new)
168 def __div__ (self, name) : 284 def __div__ (self, name) :
285 + "Buffer hiding"
169 result = self.copy() 286 result = self.copy()
170 for node in result.node() : 287 for node in result.node() :
171 result.rename_node(node.name, "[%s/%s]" % (node, name)) 288 result.rename_node(node.name, "[%s/%s]" % (node, name))
...@@ -173,6 +290,7 @@ def extend (module) : ...@@ -173,6 +290,7 @@ def extend (module) :
173 if status._value == name : 290 if status._value == name :
174 result.hide(status, status.__class__(status._name, None)) 291 result.hide(status, status.__class__(status._name, None))
175 return result 292 return result
293 + # apidoc skip
176 def __truediv__ (self, other) : 294 def __truediv__ (self, other) :
177 return self.__div__(other) 295 return self.__div__(other)
178 return PetriNet 296 return PetriNet
......
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
...@@ -382,7 +382,7 @@ class DocExtract (object) : ...@@ -382,7 +382,7 @@ class DocExtract (object) :
382 % (info["type"].get(arg, "object").strip("`"), 382 % (info["type"].get(arg, "object").strip("`"),
383 arg)) 383 arg))
384 for kw, text in sorted(info["keyword"].items()) : 384 for kw, text in sorted(info["keyword"].items()) :
385 - self.writelist("`%s`: %s" % (kw, text)) 385 + self.writelist("keyword `%s`: %s" % (kw, text))
386 if any(k in info for k in ("return", "rtype")) : 386 if any(k in info for k in ("return", "rtype")) :
387 if "return" in info : 387 if "return" in info :
388 self.writelist("`return %s`: %s" 388 self.writelist("`return %s`: %s"
......