petri_net 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.ruby-version +1 -0
- data/CHANGELOG +8 -0
- data/LICENSE +21 -0
- data/README.rdoc +93 -0
- data/Rakefile +30 -0
- data/lib/petri_net/arc.rb +133 -0
- data/lib/petri_net/base.rb +28 -0
- data/lib/petri_net/edge.rb +38 -0
- data/lib/petri_net/marking.rb +28 -0
- data/lib/petri_net/net.rb +314 -0
- data/lib/petri_net/node.rb +44 -0
- data/lib/petri_net/place.rb +108 -0
- data/lib/petri_net/reachability_graph.rb +93 -0
- data/lib/petri_net/transition.rb +109 -0
- data/lib/petri_net.rb +35 -0
- data/test/create.rb +64 -0
- data/test/tc_arc.rb +0 -0
- data/test/tc_edge.rb +0 -0
- data/test/tc_graph.rb +0 -0
- data/test/tc_node.rb +0 -0
- data/test/tc_petri_net.rb +320 -0
- data/test/tc_place.rb +0 -0
- data/test/tc_transition.rb +6 -0
- data/test/ts_test_all.rb +2 -0
- metadata +78 -0
@@ -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"
|