petri_net 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,314 @@
1
+ class PetriNet::Net < PetriNet::Base
2
+ # Human readable name
3
+ attr_accessor :name
4
+ # Storage filename
5
+ attr_accessor :filename
6
+ # Description
7
+ attr_accessor :description
8
+ # List of places
9
+ attr_reader :places
10
+ # List of arcs
11
+ attr_reader :arcs
12
+ # List of transitions
13
+ attr_reader :transitions
14
+ # List of markings
15
+ # !depricated!
16
+ attr_reader :markings
17
+
18
+ # should not be public available attr_reader :objects # Array of all objects in net
19
+ # attr_reader :up_to_date # is true if, and only if, the cached elements are calculated AND the net hasn't changed
20
+
21
+
22
+ # Create new Petri Net definition.
23
+ #
24
+ # options may be
25
+ # * name used as a human usable identifier (defaults to 'petri_net')
26
+ # * filename (defaults to the name)
27
+ # * description (defaults to 'Petri Net')
28
+ #
29
+ # Accepts a block and yields itself
30
+ def initialize(options = {}, &block)
31
+ @name = (options[:name] or 'petri_net')
32
+ @filename = (options[:filename] or @name)
33
+ @description = (options[:description] or 'Petri Net')
34
+ @places = Hash.new
35
+ @arcs = Hash.new
36
+ @transitions = Hash.new
37
+ @markings = Hash.new
38
+ @objects = Array.new
39
+ @up_to_date = false
40
+ @w_up_to_date = false
41
+
42
+ yield self unless block == nil
43
+ end
44
+
45
+ # Adds an object to the Petri Net.
46
+ # You can add
47
+ # * PetriNet::Place
48
+ # * PetriNet::Arc
49
+ # * PetriNet::Transition
50
+ # * Array of these
51
+ #
52
+ # The Objects are added by PetriNet::Net#add_place, PetriNet::Net#add_arc and PetriNet::Net#add_transition, refer to these to get more information on how they are added
53
+ # raises an RuntimeError if a wring Type is given
54
+ #
55
+ # returns itself
56
+ def <<(object)
57
+ return if object.nil? #TODO WORKAROUND There should never be a nil here, even while merging.
58
+ case object.class.to_s
59
+ when "Array"
60
+ object.each {|o| self << o}
61
+ when "PetriNet::Place"
62
+ add_place(object)
63
+ when "PetriNet::Arc"
64
+ add_arc(object)
65
+ when "PetriNet::Transition"
66
+ add_transition(object)
67
+ else
68
+ raise "(PetriNet) Unknown object #{object.class}."
69
+ end
70
+ self
71
+ end
72
+ alias_method :add_object, :<<
73
+
74
+ # Adds a place to the list of places.
75
+ # Adds the place only if the place is valid and unique in the objects-list of the net
76
+ #
77
+ # This Method changes the structure of the PetriNet, you will have to recalculate all cached functions
78
+ def add_place(place)
79
+ if place.validate && !@places.include?(place.name)
80
+ @places[place.name] = place.id
81
+ @objects[place.id] = place
82
+ place.net = self
83
+ return place.id
84
+ end
85
+ changed_structure
86
+ return false
87
+ end
88
+
89
+ # Add an arc to the list of arcs.
90
+ #
91
+ # see PetriNet::Net#add_place
92
+ def add_arc(arc)
93
+ if (arc.validate self) && !@arcs.include?(arc.name)
94
+ if arc.need_update? self
95
+ arc.update self
96
+ end
97
+ @arcs[arc.name] = arc.id
98
+ @objects[arc.id] = arc
99
+ arc.net = self
100
+ return arc.id
101
+ end
102
+ changed_structure
103
+ return false
104
+ end
105
+
106
+ # Add a transition to the list of transitions.
107
+ #
108
+ # see PetriNet::Net#add_place
109
+ def add_transition(transition)
110
+ if transition.validate && !@transitions.include?(transition.name)
111
+ @transitions[transition.name] = transition.id
112
+ @objects[transition.id] = transition
113
+ transition.net = self
114
+ return transition.id
115
+ end
116
+ changed_structure
117
+ return false
118
+ end
119
+
120
+ # Returns the place refered by the given name
121
+ # or false if there is no place with this name
122
+ def get_place(name)
123
+ place = @objects[@places[name]]
124
+ place.nil? ? false : place
125
+ end
126
+
127
+ # Returns the transition refered by the given name
128
+ # or false if there is no transition with this name
129
+ def get_transition(name)
130
+ trans = @objects[@transitions[name]]
131
+ trans.nil? ? false : trans
132
+ end
133
+
134
+ # returns the arc refered by the given name
135
+ # or false if there is no arc with this name
136
+ def get_arc(name)
137
+ arc = @objects[@arcs[name]]
138
+ arc.nil? ? false : arc
139
+ end
140
+
141
+ # Is this Petri Net pure?
142
+ # A Petri Net is said to be pure if it has no self-loops.
143
+ def pure?
144
+ raise "Not implemented yet"
145
+ end
146
+
147
+ # Is this Petri Net ordinary?
148
+ # A Petri Net is said to be ordinary if all of its arc weights are 1's.
149
+ def ordinary?
150
+ raise "Not implemented yet"
151
+ end
152
+
153
+ # Stringify this Petri Net.
154
+ def to_s
155
+ str = "Petri Net [#{@name}]\n"
156
+ str += "----------------------------\n"
157
+ str += "Description: #{@description}\n"
158
+ str += "Filename: #{@filename}\n"
159
+ str += "\n"
160
+
161
+ str += "Places\n"
162
+ str += "----------------------------\n"
163
+ @places.each_value {|p| str += @objects[p].to_s + "\n" }
164
+ str += "\n"
165
+
166
+ str += "Transitions\n"
167
+ str += "----------------------------\n"
168
+ @transitions.each_value {|t| str += @objects[t].to_s + "\n" }
169
+ str += "\n"
170
+
171
+ str += "Arcs\n"
172
+ str += "----------------------------\n"
173
+ @arcs.each_value {|a| str += @objects[a].to_s + "\n"}
174
+ str += "\n"
175
+
176
+ return str
177
+ end
178
+
179
+ # Generate GraphViz dot string.
180
+ def to_gv
181
+ # General graph options
182
+ str = "digraph #{@name} {\n"
183
+ str += "\t// General graph options\n"
184
+ str += "\trankdir = LR;\n"
185
+ str += "\tsize = \"10.5,7.5\";\n"
186
+ str += "\tnode [ style = filled, fillcolor = white, fontsize = 8.0 ]\n"
187
+ str += "\tedge [ arrowhead = vee, arrowsize = 0.5, fontsize = 8.0 ]\n"
188
+ str += "\n"
189
+
190
+ str += "\t// Places\n"
191
+ str += "\tnode [ shape = circle ];\n"
192
+ @places.each_value {|id| str += @objects[id].to_gv }
193
+ str += "\n"
194
+
195
+ str += "\t// Transitions\n"
196
+ str += "\tnode [ shape = box, fillcolor = grey90 ];\n"
197
+ @transitions.each_value {|id| str += @objects[id].to_gv }
198
+ str += "\n"
199
+
200
+ str += "\t// Arcs\n"
201
+ @arcs.each_value {|id| str += @objects[id].to_gv }
202
+ str += "}\n" # Graph closure
203
+
204
+ return str
205
+ end
206
+
207
+ # Merges two PetriNets
208
+ # Places, transitions and arcs are equal if they have the same name and description, arcs need to have the same source and destination too). With this definition of equality the resultung net will have unique ojects.
209
+ # ATTENTION conflicting capabilities and weights will be lost and the properies of the net you merge to will be used in future
210
+ # #TODO add a parameter to affect this!
211
+ def merge(net)
212
+ return self if self.equal? net
213
+ return false if net.class.to_s != "PetriNet::Net"
214
+ self << net.get_objects
215
+ self
216
+ end
217
+
218
+ def generate_reachability_graph(unlimited = true)
219
+ raise "Not implemented yet" unless unlimited
220
+ startmarkings = get_markings
221
+ @graph = PetriNet::ReachabilityGraph.new(self)
222
+ @graph.add_node current_node = PetriNet::ReachabilityGraph::Node.new(markings: get_markings)
223
+
224
+ reachability_helper startmarkings, current_node
225
+
226
+ set_markings startmarkings
227
+ @graph
228
+ end
229
+ def generate_weight_function
230
+ @weight = Hash.new
231
+ @arcs.each_value do |id|
232
+ arc = @objects[id]
233
+ @weight[[arc.source.id,arc.destination.id]] = arc.weight
234
+ end
235
+ @w_up_to_date = true
236
+ @weight
237
+ end
238
+
239
+ def w0(x,y)
240
+ generate_weight_function unless @w_up_to_date
241
+ return @weight[[x,y]].nil? ? 0 : @weight[[x,y]]
242
+ end
243
+
244
+ def update
245
+ generate_weight_function
246
+ @up_to_date = true
247
+ end
248
+
249
+ # is true if, and only if, the cached elements are calculated AND the net hasn't changed
250
+ def update?
251
+ if @w_up_to_date && true #all up_to_date-caches!!!
252
+ @up_to_date = true
253
+ return @up_to_date
254
+ end
255
+ false
256
+ end
257
+ alias_method :up_to_date, :update?
258
+
259
+ def get_markings
260
+ @places.map{|key,pid| @objects[pid].markings.size}
261
+ end
262
+
263
+ def set_markings(markings)
264
+ i = 0
265
+ @places.each_value do |pid|
266
+ @objects[pid].set_marking markings[i]
267
+ i = i+1
268
+ end
269
+ changed_state
270
+ end
271
+
272
+ def objects_size
273
+ @objects.count{|o| !o.nil?}
274
+ end
275
+
276
+ def objects_include?(object)
277
+ @objects.include?(object)
278
+ end
279
+
280
+ def get_object(id)
281
+ @objects[id]
282
+ end
283
+
284
+ def get_objects
285
+ @objects.clone
286
+ end
287
+
288
+ def objects_find_index(object)
289
+ @objects.find_index object
290
+ end
291
+
292
+ private
293
+
294
+ def changed_structure
295
+ @w_up_to_date = false
296
+ @up_to_date = false
297
+ end
298
+
299
+ def changed_state
300
+ @up_to_date = false
301
+ end
302
+
303
+ def reachability_helper(markings, source)
304
+ @transitions.each_value do |tid|
305
+ if @objects[tid].fire
306
+ current_node = PetriNet::ReachabilityGraph::Node.new(markings: get_markings)
307
+ current_node_id = @graph.add_node current_node
308
+ @graph.add_edge PetriNet::ReachabilityGraph::Edge.new(source: source, destination: current_node) unless current_node_id < 0
309
+ reachability_helper get_markings, current_node unless (current_node_id < 0)
310
+ end
311
+ set_markings markings
312
+ end
313
+ end
314
+ end
@@ -0,0 +1,44 @@
1
+ class PetriNet::ReachabilityGraph::Node < PetriNet::Base
2
+ # human readable name
3
+ attr_reader :name
4
+ # unique ID
5
+ attr_reader :id
6
+ # Makking this node represents
7
+ attr_reader :markings
8
+ # The graph this node belongs to
9
+ attr_accessor :graph
10
+
11
+ def initialize(options = {}, &block)
12
+ @id = next_object_id
13
+ @name = (options[:name] or "Node#{@id}")
14
+ @description = (options[:description] or "Node #{@id}")
15
+ @inputs = Array.new
16
+ @outputs = Array.new
17
+ @label = (options[:label] or @name)
18
+ @markings = options[:markings]
19
+
20
+ yield self unless block.nil?
21
+ end
22
+
23
+ def validate
24
+ true
25
+ end
26
+
27
+ def gv_id
28
+ "N#{@id}"
29
+ end
30
+
31
+ def to_gv
32
+ "\t#{self.gv_id} [ label = \"#{@markings}\" ];\n"
33
+ end
34
+
35
+ def ==(object)
36
+ return false unless object.class.to_s == "PetriNet::ReachabilityGraph::Node"
37
+ @markings == object.markings
38
+ end
39
+
40
+ def to_s
41
+ "#{@id}: #{@name} (#{@markings})"
42
+ end
43
+
44
+ end
@@ -0,0 +1,108 @@
1
+ class PetriNet::Place < PetriNet::Base
2
+ # Unique ID
3
+ attr_reader :id
4
+ # Human readable name
5
+ attr_accessor :name
6
+ # description
7
+ attr_accessor :description
8
+ #Token capacity
9
+ attr_accessor :capacity
10
+ # List of input-arcs
11
+ attr_reader :inputs
12
+ # List of output-arcs
13
+ attr_reader :outputs
14
+ # Current token
15
+ attr_reader :markings
16
+ # The net this place belongs to
17
+ attr_writer :net
18
+
19
+ # Initialize a new place. Supports block configuration.
20
+ def initialize(options = {}, &block)
21
+ @id = next_object_id
22
+ @name = (options[:name] or "Place#{@id}")
23
+ @description = (options[:description] or "Place #{@id}")
24
+ @capacity = options[:capacity].nil? ? Float::INFINITY : options[:capacity]
25
+ @inputs = Array.new
26
+ @outputs = Array.new
27
+ @markings = Array.new
28
+
29
+ yield self unless block == nil
30
+ end
31
+
32
+ # Add an input arc
33
+ def add_input(arc)
34
+ @inputs << arc.id unless arc.nil?
35
+ end
36
+
37
+ # Add an output arc
38
+ def add_output(arc)
39
+ @outputs << arc.id unless arc.nil?
40
+ end
41
+
42
+ def add_marking(count = 1)
43
+ if count <= @capacity
44
+ count.times do
45
+ @markings << PetriNet::Marking.new
46
+ end
47
+ return true
48
+ else
49
+ raise "Tried to add more markings than possible"
50
+ end
51
+ end
52
+
53
+ def set_marking(count)
54
+ @markings = []
55
+ add_marking count
56
+ end
57
+
58
+ alias_method :+, :add_marking
59
+
60
+ def remove_marking(count = 1)
61
+ if @markings.size >= count
62
+ ret = @markings.pop(count)
63
+ return ret unless ret.nil?
64
+ else
65
+ raise "Tried to remove more markings that possible"
66
+ end
67
+ end
68
+ alias_method :-, :remove_marking
69
+
70
+ # GraphViz ID
71
+ def gv_id
72
+ "P#{@id}"
73
+ end
74
+
75
+ # Validate the setup of this place.
76
+ def validate
77
+ return false if @id.nil? or @id < 0
78
+ return false if @name.nil? or @name.strip.length <= 0
79
+ return false if @description.nil? or @description.strip.length <= 0
80
+ return false if @capacity.nil? or @capacity < -1
81
+ return true
82
+ end
83
+
84
+ def pretransitions
85
+ raise "Not part of a net" if @net.nil?
86
+ transitions = Array.new
87
+ places << inputs.map{|i| @net.objects[i].source}
88
+ end
89
+
90
+ def posttransitions
91
+ raise "Not part of a net" if @net.nil?
92
+ outputs.map{|o| @net.objects[o].source}
93
+ end
94
+
95
+ # Stringify this place.
96
+ def to_s
97
+ "#{@id}: #{@name} (#{@capacity == nil ? -1 : 0})"
98
+ end
99
+
100
+ # GraphViz definition
101
+ def to_gv
102
+ "\t#{self.gv_id} [ label = \"#{@name} #{@markings.size} \" ];\n"
103
+ end
104
+ def ==(object)
105
+ return true if name == object.name && description = object.description
106
+ end
107
+
108
+ end
@@ -0,0 +1,93 @@
1
+ class PetriNet::ReachabilityGraph < PetriNet::Base
2
+ def initialize(net)
3
+ @objects = Array.new
4
+ @nodes = Hash.new
5
+ @edges = Hash.new
6
+ @name = net.name
7
+ end
8
+
9
+ def add_node(node)
10
+ node_index = @objects.index node
11
+ if (!node_index.nil?)
12
+ return @objects[node_index].id * -1
13
+ end
14
+
15
+ if (node.validate && (!@nodes.include? node.name))
16
+ @objects[node.id] = node
17
+ @nodes[node.name] = node.id
18
+ node.graph = self
19
+ return node.id
20
+ end
21
+ return false
22
+ end
23
+
24
+ def add_edge(edge)
25
+ if (edge.validate && (!@edges.include? edge.name))
26
+ @objects[edge.id] = edge
27
+ @edges[edge.name] = edge.id
28
+ edge.graph = self
29
+ return edge.id
30
+ end
31
+ return false
32
+ end
33
+
34
+ # Add an object to the Petri Net.
35
+ def <<(object)
36
+ case object.class.to_s
37
+ when "Array"
38
+ object.each {|o| self << o}
39
+ when "PetriNet::ReachabilityGraph::Edge"
40
+ add_edge(object)
41
+ when "PetriNet::ReachabilityGraph::Node"
42
+ add_node(object)
43
+ else
44
+ raise "(PetriNet::ReachabilityGraph) Unknown object #{object.class}."
45
+ end
46
+ self
47
+ end
48
+ alias_method :add_object, :<<
49
+
50
+ def to_gv
51
+ # General graph options
52
+ str = "digraph #{@name} {\n"
53
+ str += "\t// General graph options\n"
54
+ str += "\trankdir = LR;\n"
55
+ str += "\tsize = \"10.5,7.5\";\n"
56
+ str += "\tnode [ style = filled, fillcolor = white, fontsize = 8.0 ]\n"
57
+ str += "\tedge [ arrowhead = vee, arrowsize = 0.5, fontsize = 8.0 ]\n"
58
+ str += "\n"
59
+
60
+ str += "\t// Nodes\n"
61
+ str += "\tnode [ shape = circle ];\n"
62
+ @nodes.each_value {|id| str += @objects[id].to_gv }
63
+ str += "\n"
64
+
65
+ str += "\t// Edges\n"
66
+ @edges.each_value {|id| str += @objects[id].to_gv }
67
+ str += "}\n" # Graph closure
68
+
69
+ return str
70
+
71
+ end
72
+
73
+ def to_s
74
+ str = "Reachability Graph [#{@name}]\n"
75
+ str += "----------------------------\n"
76
+ str += "Description: #{@description}\n"
77
+ str += "Filename: #{@filename}\n"
78
+ str += "\n"
79
+
80
+ str += "Nodes\n"
81
+ str += "----------------------------\n"
82
+ @nodes.each_value {|p| str += @objects[p].to_s + "\n" }
83
+ str += "\n"
84
+
85
+ str += "Edges\n"
86
+ str += "----------------------------\n"
87
+ @edges.each_value {|t| str += @objects[t].to_s + "\n" }
88
+ str += "\n"
89
+
90
+ return str
91
+ end
92
+
93
+ end
@@ -0,0 +1,109 @@
1
+ module PetriNet
2
+ # Transition
3
+ class Transition < PetriNet::Base
4
+ # Unique ID
5
+ attr_accessor :id
6
+ # Huan readable name
7
+ attr_accessor :name
8
+ # Description
9
+ attr_accessor :description
10
+ # List of input-arcs
11
+ attr_reader :inputs
12
+ # List of output-arcs
13
+ attr_reader :outputs
14
+ # The net this transition belongs to
15
+ attr_writer :net
16
+
17
+ # Create a new transition.
18
+ def initialize(options = {}, &block)
19
+ @id = next_object_id
20
+ @name = (options[:name] or "Transition#{@id}")
21
+ @description = (options[:description] or "Transition #{@id}")
22
+ @inputs = Array.new
23
+ @outputs = Array.new
24
+
25
+ yield self unless block == nil
26
+ end
27
+
28
+ # Add an input arc
29
+ def add_input(arc)
30
+ @inputs << arc.id unless arc.nil?
31
+ end
32
+
33
+ # Add an output arc
34
+ def add_output(arc)
35
+ @outputs << arc.id unless arc.nil?
36
+ end
37
+
38
+ # GraphViz ID
39
+ def gv_id
40
+ "T#{@id}"
41
+ end
42
+
43
+ # Validate this transition.
44
+ def validate
45
+ return false if @id < 1
46
+ return false if @name.nil? or @name.length < 1
47
+ return true
48
+ end
49
+
50
+ # Stringify this transition.
51
+ def to_s
52
+ "#{@id}: #{@name}"
53
+ end
54
+
55
+ # GraphViz definition
56
+ def to_gv
57
+ "\t#{self.gv_id} [ label = \"#{@name}\" ];\n"
58
+ end
59
+
60
+ def ==(object)
61
+ name == object.name && description = object.description
62
+ end
63
+
64
+ def preplaces
65
+ raise "Not part of a net" if @net.nil?
66
+ places = Array.new
67
+ places << @inputs.map{|i| @net.objects[i].source}
68
+ end
69
+
70
+ def postplaces
71
+ raise "Not part of a net" if @net.nil?
72
+ @outputs.map{|o| @net.objects[o].source}
73
+ end
74
+
75
+ def activated?
76
+ raise "Not part of a net" if @net.nil?
77
+ @inputs.each do |i|
78
+ return false if @net.get_object(i).source.markings.size < @net.get_object(i).weight
79
+ end
80
+
81
+ @outputs.each do |o|
82
+ return false if @net.get_object(o).destination.markings.size + @net.get_object(o).weight > @net.get_object(o).destination.capacity
83
+ end
84
+ end
85
+ alias_method :firable?, :activated?
86
+
87
+ def activate!
88
+ @inputs.each do |i|
89
+ source = @net.get_object(i).source
90
+ source.add_marking(@net.get_object(i).weight - source.markings.size)
91
+ end
92
+
93
+ #what to do with outputs, if they have a capacity
94
+ end
95
+
96
+ def fire
97
+ raise "Not part of a net" if @net.nil?
98
+ return false unless activated?
99
+ @inputs.each do |i|
100
+ @net.get_object(i).source.remove_marking @net.get_object(i).weight
101
+ end
102
+
103
+ @outputs.each do |o|
104
+ @net.get_object(o).destination.add_marking @net.get_object(o).weight
105
+ end
106
+ true
107
+ end
108
+ end
109
+ end
data/lib/petri_net.rb ADDED
@@ -0,0 +1,35 @@
1
+ #--
2
+ # Copyright (c) 2009, Brian D. Nelson (bdnelson@wildcoder.com)
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ # THE SOFTWARE.
21
+ #++
22
+
23
+ # This library provides a way to represent petri nets in ruby and do some algorithms on them as generating the Reachability Graph.
24
+
25
+ # Holds the path of the base-file petri_net.rb
26
+ PETRI_LIB_FILE_PATH = File.dirname(__FILE__)
27
+ require "#{PETRI_LIB_FILE_PATH}/petri_net/base"
28
+ require "#{PETRI_LIB_FILE_PATH}/petri_net/net"
29
+ require "#{PETRI_LIB_FILE_PATH}/petri_net/place"
30
+ require "#{PETRI_LIB_FILE_PATH}/petri_net/transition"
31
+ require "#{PETRI_LIB_FILE_PATH}/petri_net/arc"
32
+ require "#{PETRI_LIB_FILE_PATH}/petri_net/marking"
33
+ require "#{PETRI_LIB_FILE_PATH}/petri_net/reachability_graph"
34
+ require "#{PETRI_LIB_FILE_PATH}/petri_net/node"
35
+ require "#{PETRI_LIB_FILE_PATH}/petri_net/edge"