petri_net_2020 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +12 -0
  5. data/CHANGELOG +8 -0
  6. data/Gemfile +9 -0
  7. data/LICENSE +21 -0
  8. data/README.rdoc +97 -0
  9. data/Rakefile +32 -0
  10. data/lib/petri_net/arc.rb +143 -0
  11. data/lib/petri_net/base.rb +30 -0
  12. data/lib/petri_net/coverability_graph/edge.rb +14 -0
  13. data/lib/petri_net/coverability_graph/graph.rb +52 -0
  14. data/lib/petri_net/coverability_graph/node.rb +123 -0
  15. data/lib/petri_net/coverability_graph.rb +8 -0
  16. data/lib/petri_net/graph/edge.rb +64 -0
  17. data/lib/petri_net/graph/graph.rb +324 -0
  18. data/lib/petri_net/graph/node.rb +141 -0
  19. data/lib/petri_net/graph.rb +7 -0
  20. data/lib/petri_net/marking.rb +27 -0
  21. data/lib/petri_net/net.rb +457 -0
  22. data/lib/petri_net/place.rb +131 -0
  23. data/lib/petri_net/reachability_graph/edge.rb +14 -0
  24. data/lib/petri_net/reachability_graph/graph.rb +24 -0
  25. data/lib/petri_net/reachability_graph/node.rb +14 -0
  26. data/lib/petri_net/reachability_graph.rb +8 -0
  27. data/lib/petri_net/transition.rb +135 -0
  28. data/lib/petri_net/version.rb +8 -0
  29. data/lib/petri_net.rb +36 -0
  30. data/petri_net.gemspec +23 -0
  31. data/test/create.rb +64 -0
  32. data/test/reachability_graph/tc_edge.rb +0 -0
  33. data/test/reachability_graph/tc_graph.rb +201 -0
  34. data/test/reachability_graph/tc_node.rb +65 -0
  35. data/test/tc_arc.rb +0 -0
  36. data/test/tc_petri_net.rb +371 -0
  37. data/test/tc_place.rb +0 -0
  38. data/test/tc_transition.rb +7 -0
  39. data/test/ts_all.rb +4 -0
  40. data/test/ts_petri_net.rb +6 -0
  41. data/test/ts_reachability_graph.rb +5 -0
  42. metadata +137 -0
@@ -0,0 +1,324 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'graphviz'
4
+ require 'graphviz/theory'
5
+ require 'rgl/adjacency'
6
+ require 'rgl/dijkstra'
7
+ class PetriNet::InfiniteReachabilityGraphError < RuntimeError
8
+ end
9
+
10
+ class PetriNet::Graph < PetriNet::Base
11
+ # The PetriNet this graph belongs to
12
+ attr_reader :net
13
+ # all nodes from this graph
14
+ attr_reader :nodes
15
+ # all edges of this graph
16
+ attr_reader :edges
17
+
18
+ def initialize(net, options = {})
19
+ @net = net
20
+ @objects = []
21
+ @nodes = {}
22
+ @edges = {}
23
+ @name = net.name
24
+ @type = 'Reachability'
25
+ @unlimited = if options['unlimited'].nil?
26
+ true
27
+ else
28
+ options['unlimited']
29
+ end
30
+ end
31
+
32
+ def add_node(node)
33
+ if node.validate && (!@objects.include? node)
34
+ @objects[node.id] = node
35
+ @nodes[node.name] = node.id
36
+ node.graph = self
37
+ return node.id
38
+ end
39
+ if @objects.include? node
40
+ res = (@objects.index node) * -1
41
+ return res
42
+ end
43
+ false
44
+ end
45
+ alias add_node! add_node
46
+
47
+ def add_edge(edge)
48
+ if edge.validate && (!@edges.include? edge.name)
49
+ @objects[edge.id] = edge
50
+ @edges[edge.name] = edge.id
51
+ edge.graph = self
52
+ edge.source.outputs << edge.id
53
+ edge.destination.inputs << edge.id
54
+ return edge.id
55
+ end
56
+ false
57
+ end
58
+
59
+ def get_edge(source, dest)
60
+ res = nil
61
+ @edges.each_value do |edge|
62
+ if @objects[edge].source == source && @objects[edge].destination == dest
63
+ res = @objects[edge]
64
+ end
65
+ end
66
+ res
67
+ end
68
+
69
+ # Add an object to the Graph.
70
+ def <<(object)
71
+ case object.class.to_s
72
+ when 'Array'
73
+ object.each { |o| self << o }
74
+ when 'PetriNet::ReachabilityGraph::Edge'
75
+ add_edge(object)
76
+ when 'PetriNet::ReachabilityGraph::Node'
77
+ add_node(object)
78
+ else
79
+ raise "(PetriNet::ReachabilityGraph) Unknown object #{object.class}."
80
+ end
81
+ self
82
+ end
83
+ alias add_object <<
84
+
85
+ def get_node(node)
86
+ return @objects[node] if node.class.to_s == 'Fixnum'
87
+ if node.class.to_s == 'Array'
88
+ return @objects.select { |o| o.class.to_s == 'PetriNet::ReachabilityGraph::Node' && o.markings == node }.first
89
+ end
90
+ end
91
+
92
+ def get_object(id)
93
+ @objects[id]
94
+ end
95
+
96
+ def get_nodes
97
+ res = []
98
+ @nodes.each_value do |n|
99
+ res << @objects[n]
100
+ end
101
+ res
102
+ end
103
+
104
+ def infinite?
105
+ @nodes.each_value do |node|
106
+ return true if @objects[node].infinite?
107
+ end
108
+ false
109
+ end
110
+
111
+ def to_gv(output = 'png', filename = '')
112
+ g = generate_gv
113
+ filename = "#{@name}_graph.png" if filename.empty?
114
+ g.output(png: filename) if output == 'png'
115
+ g.output
116
+ end
117
+
118
+ def generate_gv(named = true)
119
+ g = GraphViz.new(:G, type: :digraph)
120
+
121
+ @nodes.each_value do |node|
122
+ label = if named
123
+ (@net.get_place_from_marking @objects[node].markings).to_s
124
+ else
125
+ @objects[node].markings.to_s
126
+ end
127
+ gv_node = g.add_nodes(label)
128
+ gv_node.set do |n|
129
+ n.label = '*' + label + '*' if @objects[node].start
130
+ end
131
+ end
132
+ @edges.each_value do |edge|
133
+ label1 = if named
134
+ (@net.get_place_from_marking @objects[edge].source.markings).to_s
135
+ else
136
+ @objects[edge].source.markings.to_s
137
+ end
138
+
139
+ label2 = if named
140
+ (@net.get_place_from_marking @objects[edge].destination.markings).to_s
141
+ else
142
+ @objects[edge].destination.markings.to_s
143
+ end
144
+ gv_edge = g.add_edges(label1, label2)
145
+ gv_edge.set do |e|
146
+ e.label = @objects[edge].transition
147
+ end
148
+ end
149
+ @gv = g
150
+ end
151
+
152
+ def generate_rgl
153
+ g = RGL::DirectedAdjacencyGraph.new
154
+ @nodes.each_value do |node|
155
+ g.add_vertex @objects[node].markings.to_s
156
+ end
157
+ @edges.each_value do |edge|
158
+ g.add_edge @objects[edge].source.markings.to_s, @objects[edge].destination.markings.to_s
159
+ end
160
+ @rgl = g
161
+ end
162
+
163
+ def to_s
164
+ str = "#{@type} Graph [#{@name}]\n"
165
+ str += "----------------------------\n"
166
+ str += "Description: #{@description}\n"
167
+ str += "Filename: #{@filename}\n"
168
+ str += "\n"
169
+
170
+ str += "Nodes\n"
171
+ str += "----------------------------\n"
172
+ @nodes.each_value { |p| str += @objects[p].to_s + "\n" }
173
+ str += "\n"
174
+
175
+ str += "Edges\n"
176
+ str += "----------------------------\n"
177
+ @edges.each_value { |t| str += @objects[t].to_s + "\n" }
178
+ str += "\n"
179
+
180
+ str
181
+ end
182
+
183
+ def get_rgl
184
+ generate_rgl if @rgl.nil?
185
+ @rgl
186
+ end
187
+
188
+ def cycles
189
+ get_rgl.cycles
190
+ end
191
+
192
+ def shortest_path(start, destination)
193
+ g = get_rgl
194
+ weights = ->(_edge) { 1 }
195
+ g.dijkstra_shortest_path(weights, start.to_s, destination.to_s)
196
+ end
197
+
198
+ def path_probability(path)
199
+ sanitize_probabilities
200
+ prob = 1
201
+ counter = 0
202
+ path.each do |node|
203
+ edge = get_edge(path[counter + 1], node)
204
+ prob *= edge.probability unless edge.nil? # last node has no pre-edge
205
+ counter = counter += 1
206
+ end
207
+ prob
208
+ end
209
+
210
+ def node_probability(start, node)
211
+ paths = get_paths_without_loops(start, node)
212
+ prob = 0
213
+ paths.each do |path|
214
+ prob += (path_probability path)
215
+ end
216
+ prob
217
+ end
218
+
219
+ def best_path(start, node)
220
+ paths = get_paths_without_loops(start, node)
221
+ prob = 0
222
+ res_path = nil
223
+ paths.each do |path|
224
+ if (path_probability path) >= prob
225
+ prob = (path_probability path)
226
+ res_path = path
227
+ end
228
+ end
229
+ [res_path, prob]
230
+ end
231
+
232
+ def worst_path(start, node)
233
+ paths = get_paths_without_loops(start, node)
234
+ prob = 1
235
+ res_path = nil
236
+ paths.each do |path|
237
+ if (path_probability path) <= prob
238
+ prob = (path_probability path)
239
+ res_path = path
240
+ end
241
+ end
242
+ [res_path, prob]
243
+ end
244
+
245
+ def get_most_relative_influence_on_path(start, node, factor = 0.5)
246
+ prob_counter = 0
247
+ prob_diff = []
248
+ paths = get_paths_without_loops(start, node)
249
+ paths.each do |path|
250
+ counter = 0
251
+ path.each do |curr_node|
252
+ prob_before = node_probability start, node
253
+ next if node == path[-1]
254
+
255
+ edge = get_edge(path[counter + 1], curr_node)
256
+ save = edge.probability unless edge.nil?
257
+ edge.probability += (1 - edge.probability) * factor unless edge.nil?
258
+ prob_after = node_probability start, node unless edge.nil?
259
+ edge.probability = save unless edge.nil?
260
+ prob_diff[prob_counter] = [prob_after - prob_before, [curr_node, path[counter + 1]]] unless edge.nil?
261
+ prob_counter += 1 unless edge.nil?
262
+ counter += 1
263
+ end
264
+ end
265
+ prob_diff.max { |x, y| x[0] <=> y[0] }
266
+ end
267
+
268
+ def get_most_absolute_influence_on_path(start, node, sum = 0.01)
269
+ prob_counter = 0
270
+ prob_diff = []
271
+ paths = get_paths_without_loops(start, node)
272
+ paths.each do |path|
273
+ counter = 0
274
+ path.each do |curr_node|
275
+ prob_before = node_probability start, node
276
+ next if node == path[-1]
277
+
278
+ edge = get_edge(path[counter + 1], curr_node)
279
+ save = edge.probability unless edge.nil?
280
+ edge.probability = edge.probability + sum unless edge.nil?
281
+ edge.probability = 1 if !edge.nil? && edge.probability > 1
282
+ prob_after = node_probability start, node unless edge.nil?
283
+ edge.probability = save unless edge.nil?
284
+ prob_diff[prob_counter] = [prob_after - prob_before, [curr_node, path[counter + 1]]] unless edge.nil?
285
+ prob_counter += 1 unless edge.nil?
286
+ counter += 1
287
+ end
288
+ end
289
+ prob_diff.max { |x, y| x[0] <=> y[0] }
290
+ end
291
+
292
+ def get_paths_without_loops(start, goal)
293
+ get_paths_without_loops_helper(get_node(start), get_node(goal))
294
+ end
295
+
296
+ def sanitize_probabilities
297
+ @nodes.each_value do |node|
298
+ prob = 1.0
299
+ @objects[node].outputs.each do |edge|
300
+ prob += @objects[edge].probability unless @objects[edge].probability.nil?
301
+ end
302
+ @objects[node].outputs.each do |edge|
303
+ @objects[edge].probability = prob / @objects[node].outputs.size if @objects[edge].probability.nil?
304
+ end
305
+ end
306
+ end
307
+
308
+ private
309
+
310
+ def get_paths_without_loops_helper(start, goal, reverse_paths = [], reverse_path = [])
311
+ if goal == start
312
+ reverse_path << goal
313
+ reverse_paths << reverse_path
314
+ return nil
315
+ end
316
+ return nil if reverse_path.include? goal
317
+
318
+ path = []
319
+ goal.inputs.each do |input|
320
+ get_paths_without_loops_helper(start, @objects[input].source, reverse_paths, reverse_path.clone << goal)
321
+ end
322
+ reverse_paths
323
+ end
324
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PetriNet::Graph::Node < PetriNet::Base
4
+ include Comparable
5
+
6
+ # human readable name
7
+ attr_reader :name
8
+ # unique ID
9
+ attr_reader :id
10
+ # Makking this node represents
11
+ attr_reader :markings
12
+ # The graph this node belongs to
13
+ attr_accessor :graph
14
+ # Omega-marked node (unlimited Petrinet -> coverabilitygraph)
15
+ attr_reader :omega_marked
16
+ # Incoming edges
17
+ attr_reader :inputs
18
+ # Outgoing edges
19
+ attr_reader :outputs
20
+ # Label of the node
21
+ attr_reader :label
22
+ # True if this is the start-marking
23
+ attr_reader :start
24
+
25
+ def initialize(graph, options = {}, &block)
26
+ @graph = graph
27
+ @id = next_object_id
28
+ @name = (options[:name] || "Node#{@id}")
29
+ @description = (options[:description] || "Node #{@id}")
30
+ @inputs = []
31
+ @outputs = []
32
+ @label = (options[:label] || @name)
33
+ @markings = options[:markings]
34
+ @start = (options[:start] || false)
35
+ raise ArgumentError, 'Every Node needs markings' if @markings.nil?
36
+
37
+ @omega_marked = if @markings.include? Float::INFINITY
38
+ true
39
+ else
40
+ false
41
+ end
42
+
43
+ yield self unless block.nil?
44
+ end
45
+
46
+ def infinite?
47
+ @omega_marked
48
+ end
49
+
50
+ # Add an omega-marking to a specified place
51
+ def add_omega(object)
52
+ ret = []
53
+ if object.class.to_s == 'PetriNet::CoverabilityGraph::Node'
54
+ if self < object
55
+ counter = 0
56
+ object.markings.each do |marking|
57
+ if @markings[counter] < marking
58
+ @markings[counter] = Float::INFINITY
59
+ ret << counter
60
+ end
61
+ counter += 1
62
+ end
63
+ else
64
+ return false
65
+ end
66
+ elsif object.class.to_s == 'Array'
67
+ object.each do |place|
68
+ markings[place] = Float::INFINITY
69
+ ret = object
70
+ end
71
+ elsif object.class.to_s == 'Fixnum'
72
+ markings[object] = Float::INFINITY
73
+ ret = [object]
74
+ elsif object.class.to_s == 'PetriNet::ReachabilityGraph::Node'
75
+ raise PetriNet::Graph::InfinityError('ReachabilityGraphs do not support omega-markings')
76
+ end
77
+ @omega_marked = true
78
+ ret
79
+ end
80
+
81
+ def include_place(place)
82
+ places = @graph.net.get_place_list
83
+ included_places = []
84
+ i = 0
85
+ @markings.each do |m|
86
+ included_places << places[i] if m > 0
87
+ i += 1
88
+ end
89
+ included_places.include? place
90
+ end
91
+
92
+ def validate
93
+ true
94
+ end
95
+
96
+ def gv_id
97
+ "N#{@id}"
98
+ end
99
+
100
+ def to_gv
101
+ "\t#{gv_id} [ label = \"#{@markings}\" ];\n"
102
+ end
103
+
104
+ # Compare-operator, other Operators are available through comparable-mixin
105
+ def <=>(object)
106
+ return nil unless object.class.to_s == 'PetriNet::ReachabilityGraph::Node'
107
+ return 0 if @markings == object.markings
108
+
109
+ counter = 0
110
+ less = true
111
+ markings.each do |marking|
112
+ if marking <= object.markings[counter] && less
113
+ less = true
114
+ else
115
+ less = false
116
+ break
117
+ end
118
+ counter += 1
119
+ end
120
+ return -1 if less
121
+
122
+ counter = 0
123
+ more = true
124
+ markings.each do |marking|
125
+ if marking >= object.markings[counter] && more
126
+ more = true
127
+ else
128
+ more = false
129
+ break
130
+ end
131
+ counter += 1
132
+ end
133
+ return 1 if more
134
+
135
+ nil
136
+ end
137
+
138
+ def to_s
139
+ "#{@id}: #{@name} (#{@markings})"
140
+ end
141
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'graph/graph'
4
+ require_relative 'graph/node'
5
+ require_relative 'graph/edge'
6
+ class PetriNet::Graph < PetriNet::Base
7
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PetriNet
4
+ # Marking
5
+ class Marking < PetriNet::Base
6
+ # depricated
7
+ attr_accessor :id # Unique ID
8
+ # depricated
9
+ attr_accessor :name # Human readable name
10
+ # depricated
11
+ attr_accessor :description # Description
12
+ # depricated
13
+ attr_accessor :timestep # Marking timestep
14
+
15
+ # Create a new marking.
16
+ # depricated
17
+ def initialize(_options = {}, &block)
18
+ yield self unless block.nil?
19
+ end
20
+
21
+ # Validate this marking.
22
+ def validate; end
23
+
24
+ # Stringify this marking.
25
+ def to_s; end
26
+ end
27
+ end