petri_net_2020 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.ruby-version +1 -0
- data/.travis.yml +12 -0
- data/CHANGELOG +8 -0
- data/Gemfile +9 -0
- data/LICENSE +21 -0
- data/README.rdoc +97 -0
- data/Rakefile +32 -0
- data/lib/petri_net/arc.rb +143 -0
- data/lib/petri_net/base.rb +30 -0
- data/lib/petri_net/coverability_graph/edge.rb +14 -0
- data/lib/petri_net/coverability_graph/graph.rb +52 -0
- data/lib/petri_net/coverability_graph/node.rb +123 -0
- data/lib/petri_net/coverability_graph.rb +8 -0
- data/lib/petri_net/graph/edge.rb +64 -0
- data/lib/petri_net/graph/graph.rb +324 -0
- data/lib/petri_net/graph/node.rb +141 -0
- data/lib/petri_net/graph.rb +7 -0
- data/lib/petri_net/marking.rb +27 -0
- data/lib/petri_net/net.rb +457 -0
- data/lib/petri_net/place.rb +131 -0
- data/lib/petri_net/reachability_graph/edge.rb +14 -0
- data/lib/petri_net/reachability_graph/graph.rb +24 -0
- data/lib/petri_net/reachability_graph/node.rb +14 -0
- data/lib/petri_net/reachability_graph.rb +8 -0
- data/lib/petri_net/transition.rb +135 -0
- data/lib/petri_net/version.rb +8 -0
- data/lib/petri_net.rb +36 -0
- data/petri_net.gemspec +23 -0
- data/test/create.rb +64 -0
- data/test/reachability_graph/tc_edge.rb +0 -0
- data/test/reachability_graph/tc_graph.rb +201 -0
- data/test/reachability_graph/tc_node.rb +65 -0
- data/test/tc_arc.rb +0 -0
- data/test/tc_petri_net.rb +371 -0
- data/test/tc_place.rb +0 -0
- data/test/tc_transition.rb +7 -0
- data/test/ts_all.rb +4 -0
- data/test/ts_petri_net.rb +6 -0
- data/test/ts_reachability_graph.rb +5 -0
- metadata +137 -0
@@ -0,0 +1,457 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'graphviz'
|
5
|
+
require 'matrix'
|
6
|
+
require 'bigdecimal/ludcmp'
|
7
|
+
|
8
|
+
class PetriNet::Net < PetriNet::Base
|
9
|
+
include LUSolve
|
10
|
+
# Human readable name
|
11
|
+
attr_accessor :name
|
12
|
+
# Storage filename
|
13
|
+
attr_accessor :filename
|
14
|
+
# Description
|
15
|
+
attr_accessor :description
|
16
|
+
# List of places
|
17
|
+
attr_reader :places
|
18
|
+
# List of arcs
|
19
|
+
attr_reader :arcs
|
20
|
+
# List of transitions
|
21
|
+
attr_reader :transitions
|
22
|
+
# List of markings
|
23
|
+
# !depricated!
|
24
|
+
attr_reader :markings
|
25
|
+
|
26
|
+
# should not be public available attr_reader :objects # Array of all objects in net
|
27
|
+
# attr_reader :up_to_date # is true if, and only if, the cached elements are calculated AND the net hasn't changed
|
28
|
+
|
29
|
+
# Create new Petri Net definition.
|
30
|
+
#
|
31
|
+
# options may be
|
32
|
+
# * name used as a human usable identifier (defaults to 'petri_net')
|
33
|
+
# * filename (defaults to the name)
|
34
|
+
# * description (defaults to 'Petri Net')
|
35
|
+
#
|
36
|
+
# Accepts a block and yields itself
|
37
|
+
def initialize(options = {}, &block)
|
38
|
+
@name = (options[:name] || 'petri_net')
|
39
|
+
@filename = (options[:filename] || @name)
|
40
|
+
@description = (options[:description] || 'Petri Net')
|
41
|
+
@places = {}
|
42
|
+
@arcs = {}
|
43
|
+
@transitions = {}
|
44
|
+
@markings = {}
|
45
|
+
@objects = []
|
46
|
+
@up_to_date = false
|
47
|
+
@w_up_to_date = false
|
48
|
+
|
49
|
+
yield self unless block.nil?
|
50
|
+
end
|
51
|
+
|
52
|
+
# Adds an object to the Petri Net.
|
53
|
+
# You can add
|
54
|
+
# * PetriNet::Place
|
55
|
+
# * PetriNet::Arc
|
56
|
+
# * PetriNet::Transition
|
57
|
+
# * Array of these
|
58
|
+
#
|
59
|
+
# 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
|
60
|
+
# raises an RuntimeError if a wring Type is given
|
61
|
+
#
|
62
|
+
# returns itself
|
63
|
+
def <<(object)
|
64
|
+
return if object.nil? # TODO: WORKAROUND There should never be a nil here, even while merging.
|
65
|
+
|
66
|
+
case object.class.to_s
|
67
|
+
when 'Array'
|
68
|
+
object.each { |o| self << o }
|
69
|
+
when 'PetriNet::Place'
|
70
|
+
add_place(object)
|
71
|
+
when 'PetriNet::Arc'
|
72
|
+
add_arc(object)
|
73
|
+
when 'PetriNet::Transition'
|
74
|
+
add_transition(object)
|
75
|
+
else
|
76
|
+
raise "(PetriNet) Unknown object #{object.class}."
|
77
|
+
end
|
78
|
+
self
|
79
|
+
end
|
80
|
+
alias add_object <<
|
81
|
+
|
82
|
+
# Adds a place to the list of places.
|
83
|
+
# Adds the place only if the place is valid and unique in the objects-list of the net
|
84
|
+
#
|
85
|
+
# This Method changes the structure of the PetriNet, you will have to recalculate all cached functions
|
86
|
+
def add_place(place)
|
87
|
+
if place.validate && !@places.include?(place.name)
|
88
|
+
@places[place.name] = place.id
|
89
|
+
@objects[place.id] = place
|
90
|
+
place.net = self
|
91
|
+
return place.id
|
92
|
+
end
|
93
|
+
changed_structure
|
94
|
+
false
|
95
|
+
end
|
96
|
+
|
97
|
+
# Add an arc to the list of arcs.
|
98
|
+
#
|
99
|
+
# see PetriNet::Net#add_place
|
100
|
+
def add_arc(arc)
|
101
|
+
if (arc.validate self) && !@arcs.include?(arc.name)
|
102
|
+
arc.update self if arc.need_update? self
|
103
|
+
@arcs[arc.name] = arc.id
|
104
|
+
@objects[arc.id] = arc
|
105
|
+
arc.net = self
|
106
|
+
return arc.id
|
107
|
+
end
|
108
|
+
changed_structure
|
109
|
+
false
|
110
|
+
end
|
111
|
+
|
112
|
+
# Add a transition to the list of transitions.
|
113
|
+
#
|
114
|
+
# see PetriNet::Net#add_place
|
115
|
+
def add_transition(transition)
|
116
|
+
if transition.validate && !@transitions.include?(transition.name)
|
117
|
+
@transitions[transition.name] = transition.id
|
118
|
+
@objects[transition.id] = transition
|
119
|
+
transition.net = self
|
120
|
+
return transition.id
|
121
|
+
end
|
122
|
+
changed_structure
|
123
|
+
false
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns the place refered by the given name
|
127
|
+
# or false if there is no place with this name
|
128
|
+
def get_place(name)
|
129
|
+
place = @objects[@places[name]]
|
130
|
+
place.nil? ? false : place
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns the transition refered by the given name
|
134
|
+
# or false if there is no transition with this name
|
135
|
+
def get_transition(name)
|
136
|
+
trans = @objects[@transitions[name]]
|
137
|
+
trans.nil? ? false : trans
|
138
|
+
end
|
139
|
+
|
140
|
+
# returns the arc refered by the given name
|
141
|
+
# or false if there is no arc with this name
|
142
|
+
def get_arc(name)
|
143
|
+
arc = @objects[@arcs[name]]
|
144
|
+
arc.nil? ? false : arc
|
145
|
+
end
|
146
|
+
|
147
|
+
# Is this Petri Net pure?
|
148
|
+
# A Petri Net is said to be pure if it has no self-loops.
|
149
|
+
def pure?
|
150
|
+
raise 'Not implemented yet'
|
151
|
+
end
|
152
|
+
|
153
|
+
# Is this Petri Net ordinary?
|
154
|
+
# A Petri Net is said to be ordinary if all of its arc weights are 1's.
|
155
|
+
def ordinary?
|
156
|
+
raise 'Not implemented yet'
|
157
|
+
end
|
158
|
+
|
159
|
+
# Stringify this Petri Net.
|
160
|
+
def to_s
|
161
|
+
str =
|
162
|
+
%(Petri Net [#{@name}]
|
163
|
+
----------------------------
|
164
|
+
Description: #{@description}
|
165
|
+
Filename: #{@filename}
|
166
|
+
|
167
|
+
Places
|
168
|
+
----------------------------
|
169
|
+
#{str = ''; @places.each_value { |p| str += @objects[p].to_s + "\n" }; str}
|
170
|
+
Transitions
|
171
|
+
----------------------------
|
172
|
+
#{str = ''; @transitions.each_value { |t| str += @objects[t].to_s + "\n" }; str}
|
173
|
+
Arcs
|
174
|
+
----------------------------
|
175
|
+
#{str = ''; @arcs.each_value { |a| str += @objects[a].to_s + "\n" }; str}
|
176
|
+
)
|
177
|
+
str
|
178
|
+
end
|
179
|
+
|
180
|
+
def to_gv(output = 'png', filename = '')
|
181
|
+
g = generate_gv
|
182
|
+
filename = "#{@name}_net.png" if filename.empty?
|
183
|
+
g.output(png: filename) if output == 'png'
|
184
|
+
g.output
|
185
|
+
end
|
186
|
+
|
187
|
+
def generate_gv
|
188
|
+
g = GraphViz.new(:G, type: :digraph)
|
189
|
+
|
190
|
+
@places.each_value do |place|
|
191
|
+
gv_node = g.add_nodes(@objects[place].name)
|
192
|
+
end
|
193
|
+
@transitions.each_value do |transition|
|
194
|
+
gv_node = g.add_nodes(@objects[transition].name)
|
195
|
+
gv_node.shape = :box
|
196
|
+
gv_node.fillcolor = :grey90
|
197
|
+
end
|
198
|
+
@arcs.each_value do |arc|
|
199
|
+
gv_edge = g.add_edges(@objects[arc].source.name, @objects[arc].destination.name)
|
200
|
+
end
|
201
|
+
g
|
202
|
+
end
|
203
|
+
|
204
|
+
# Generate GraphViz dot string.
|
205
|
+
def to_gv
|
206
|
+
# General graph options
|
207
|
+
str = "digraph #{@name} {\n"
|
208
|
+
str += "\t// General graph options\n"
|
209
|
+
str += "\trankdir = LR;\n"
|
210
|
+
str += "\tsize = \"10.5,7.5\";\n"
|
211
|
+
str += "\tnode [ style = filled, fillcolor = white, fontsize = 8.0 ]\n"
|
212
|
+
str += "\tedge [ arrowhead = vee, arrowsize = 0.5, fontsize = 8.0 ]\n"
|
213
|
+
str += "\n"
|
214
|
+
|
215
|
+
str += "\t// Places\n"
|
216
|
+
str += "\tnode [ shape = circle ];\n"
|
217
|
+
@places.each_value { |id| str += @objects[id].to_gv }
|
218
|
+
str += "\n"
|
219
|
+
|
220
|
+
str += "\t// Transitions\n"
|
221
|
+
str += "\tnode [ shape = box, fillcolor = grey90 ];\n"
|
222
|
+
@transitions.each_value { |id| str += @objects[id].to_gv }
|
223
|
+
str += "\n"
|
224
|
+
|
225
|
+
str += "\t// Arcs\n"
|
226
|
+
@arcs.each_value { |id| str += @objects[id].to_gv }
|
227
|
+
str += "}\n" # Graph closure
|
228
|
+
|
229
|
+
str
|
230
|
+
end
|
231
|
+
|
232
|
+
# Merges two PetriNets
|
233
|
+
# 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.
|
234
|
+
# ATTENTION conflicting capabilities and weights will be lost and the properies of the net you merge to will be used in future
|
235
|
+
# #TODO add a parameter to affect this!
|
236
|
+
def merge(net)
|
237
|
+
return self if equal? net
|
238
|
+
return false if net.class.to_s != 'PetriNet::Net'
|
239
|
+
|
240
|
+
self << net.get_objects
|
241
|
+
self
|
242
|
+
end
|
243
|
+
|
244
|
+
def reachability_graph
|
245
|
+
update unless @up_to_date
|
246
|
+
generate_reachability_graph unless @graph && @up_to_date
|
247
|
+
@graph
|
248
|
+
end
|
249
|
+
|
250
|
+
def generate_coverability_graph
|
251
|
+
startmarkings = get_markings
|
252
|
+
@graph = PetriNet::CoverabilityGraph.new(self)
|
253
|
+
@graph.add_node current_node = PetriNet::CoverabilityGraph::Node.new(@graph, markings: get_markings, start: true)
|
254
|
+
|
255
|
+
coverability_helper startmarkings, current_node
|
256
|
+
|
257
|
+
set_markings startmarkings
|
258
|
+
@graph
|
259
|
+
end
|
260
|
+
|
261
|
+
def generate_reachability_graph
|
262
|
+
startmarkings = get_markings
|
263
|
+
@graph = PetriNet::ReachabilityGraph.new(self)
|
264
|
+
@graph.add_node current_node = PetriNet::ReachabilityGraph::Node.new(@graph, markings: get_markings, start: true)
|
265
|
+
|
266
|
+
reachability_helper startmarkings, current_node
|
267
|
+
|
268
|
+
set_markings startmarkings
|
269
|
+
@graph
|
270
|
+
end
|
271
|
+
|
272
|
+
def generate_weight_function
|
273
|
+
@weight = {}
|
274
|
+
@arcs.each_value do |id|
|
275
|
+
arc = @objects[id]
|
276
|
+
@weight[[arc.source.id, arc.destination.id]] = arc.weight
|
277
|
+
end
|
278
|
+
@w_up_to_date = true
|
279
|
+
@weight
|
280
|
+
end
|
281
|
+
|
282
|
+
def w0(x, y)
|
283
|
+
generate_weight_function unless @w_up_to_date
|
284
|
+
@weight[[x, y]].nil? ? 0 : @weight[[x, y]]
|
285
|
+
end
|
286
|
+
|
287
|
+
def update
|
288
|
+
generate_weight_function
|
289
|
+
@up_to_date = true
|
290
|
+
end
|
291
|
+
|
292
|
+
# is true if, and only if, the cached elements are calculated AND the net hasn't changed
|
293
|
+
def update?
|
294
|
+
if @w_up_to_date && true # all up_to_date-caches!!!
|
295
|
+
@up_to_date = true
|
296
|
+
return @up_to_date
|
297
|
+
end
|
298
|
+
false
|
299
|
+
end
|
300
|
+
alias up_to_date update?
|
301
|
+
|
302
|
+
def get_markings
|
303
|
+
@places.map { |_key, pid| @objects[pid].markings.size }
|
304
|
+
end
|
305
|
+
|
306
|
+
def get_marking(places)
|
307
|
+
places = [places] unless places.class.to_s == 'Array'
|
308
|
+
places.map! { |p| get_place p } if places.first.class.to_s == 'Fixnum'
|
309
|
+
res = []
|
310
|
+
get_place_list.map { |place| res << ((places.include? place.name) ? 1 : 0) }
|
311
|
+
res
|
312
|
+
end
|
313
|
+
|
314
|
+
def set_markings(markings)
|
315
|
+
i = 0
|
316
|
+
@places.each_value do |pid|
|
317
|
+
@objects[pid].set_marking markings[i]
|
318
|
+
i += 1
|
319
|
+
end
|
320
|
+
changed_state
|
321
|
+
end
|
322
|
+
|
323
|
+
def get_place_list
|
324
|
+
@places.map { |_key, pid| @objects[pid] }
|
325
|
+
end
|
326
|
+
|
327
|
+
def get_place_from_marking(marking)
|
328
|
+
return marking if marking.count(1) != 1
|
329
|
+
|
330
|
+
get_place_list[marking.index(1)].name
|
331
|
+
end
|
332
|
+
|
333
|
+
def objects_size
|
334
|
+
@objects.count { |o| !o.nil? }
|
335
|
+
end
|
336
|
+
|
337
|
+
def objects_include?(object)
|
338
|
+
@objects.include?(object)
|
339
|
+
end
|
340
|
+
|
341
|
+
def get_object(id)
|
342
|
+
@objects[id]
|
343
|
+
end
|
344
|
+
|
345
|
+
def get_objects
|
346
|
+
@objects.clone
|
347
|
+
end
|
348
|
+
|
349
|
+
def objects_find_index(object)
|
350
|
+
@objects.find_index object
|
351
|
+
end
|
352
|
+
|
353
|
+
def save(filename)
|
354
|
+
File.open(filename, 'w') { |_f| @net.to_yaml }
|
355
|
+
end
|
356
|
+
|
357
|
+
def load(filename)
|
358
|
+
@net = YAML.safe_load(File.read(filename))
|
359
|
+
end
|
360
|
+
|
361
|
+
def fire(transition)
|
362
|
+
get_transition(transition).fire
|
363
|
+
end
|
364
|
+
|
365
|
+
def delta
|
366
|
+
generate_delta if @delta.nil?
|
367
|
+
@delta
|
368
|
+
end
|
369
|
+
|
370
|
+
def t_invariants
|
371
|
+
delta = self.delta
|
372
|
+
zero_vector = []
|
373
|
+
delta.row_count.times { zero_vector << 0 }
|
374
|
+
zero = BigDecimal('0.0')
|
375
|
+
one = BigDecimal('1.0')
|
376
|
+
|
377
|
+
ps = ludecomp(delta.t.to_a.flatten.map { |i| BigDecimal(i, 16) }, delta.row_count, zero, one)
|
378
|
+
x = lusolve(delta.t.to_a.flatten.map { |i| BigDecimal(i, 16) }, zero_vector.map { |i| BigDecimal(i, 16) }, ps, zero)
|
379
|
+
|
380
|
+
x
|
381
|
+
end
|
382
|
+
|
383
|
+
def s_invariant
|
384
|
+
raise 'Not jet implemented'
|
385
|
+
end
|
386
|
+
|
387
|
+
private
|
388
|
+
|
389
|
+
def generate_delta
|
390
|
+
d = Array.new(@places.size) { Array.new(@transitions.size) }
|
391
|
+
i = 0
|
392
|
+
@places.each do |_p_key, p_value|
|
393
|
+
j = 0
|
394
|
+
@transitions.each do |_t_key, t_value|
|
395
|
+
d[i][j] = w0(t_value, p_value) - w0(p_value, t_value)
|
396
|
+
j += 1
|
397
|
+
end
|
398
|
+
i += 1
|
399
|
+
end
|
400
|
+
@delta = Matrix[d]
|
401
|
+
end
|
402
|
+
|
403
|
+
def changed_structure
|
404
|
+
@w_up_to_date = false
|
405
|
+
@up_to_date = false
|
406
|
+
end
|
407
|
+
|
408
|
+
def changed_state
|
409
|
+
@up_to_date = false
|
410
|
+
end
|
411
|
+
|
412
|
+
def reachability_helper(markings, source)
|
413
|
+
@transitions.each_value do |tid|
|
414
|
+
raise PetriNet::ReachabilityGraph::InfinityGraphError if @objects[tid].inputs.empty? && !@objects[tid].outputs.empty?
|
415
|
+
next if @objects[tid].inputs.empty?
|
416
|
+
|
417
|
+
if @objects[tid].fire
|
418
|
+
current_node = PetriNet::ReachabilityGraph::Node.new(@graph, markings: get_markings)
|
419
|
+
begin
|
420
|
+
node_id = @graph.add_node current_node
|
421
|
+
rescue StandardError
|
422
|
+
@graph.add_node! current_node
|
423
|
+
@graph.add_edge PetriNet::ReachabilityGraph::Edge.new(@graph, source: source, destination: current_node)
|
424
|
+
infinity_node = PetriNet::ReachabilityGraph::InfinityNode.new(@graph)
|
425
|
+
@graph.add_node infinity_node
|
426
|
+
@graph.add_edge PetriNet::ReachabilityGraph::Edge.new(@graph, source: current_node, destination: infinity_node)
|
427
|
+
next
|
428
|
+
end
|
429
|
+
current_node = @graph.get_node node_id.abs if node_id < 0
|
430
|
+
@graph.add_edge PetriNet::ReachabilityGraph::Edge.new(@graph, source: source, destination: current_node, probability: @objects[tid].probability) # if node_id
|
431
|
+
reachability_helper get_markings, current_node if node_id >= 0
|
432
|
+
end
|
433
|
+
set_markings markings
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
def coverability_helper(markings, source, added_omega = false)
|
438
|
+
@transitions.each_value do |tid|
|
439
|
+
if @objects[tid].fire
|
440
|
+
current_node = PetriNet::ReachabilityGraph::Node.new(@graph, markings: get_markings)
|
441
|
+
current_node_id = @graph.add_node current_node
|
442
|
+
@graph.add_edge PetriNet::ReachabilityGraph::Edge.new(@graph, source: source, destination: current_node, probability: @objects[tid].probability, transition: @objects[tid].name) if current_node_id >= 0
|
443
|
+
omega = false
|
444
|
+
if current_node_id != -Float::INFINITY && current_node_id < 0 && @graph.get_node(current_node_id * -1) != current_node
|
445
|
+
omega = true
|
446
|
+
added_omega_old = added_omega
|
447
|
+
added_omega = @graph.get_node(current_node_id * -1).add_omega current_node
|
448
|
+
break if added_omega_old == added_omega
|
449
|
+
|
450
|
+
@graph.add_edge PetriNet::ReachabilityGraph::Edge.new(@graph, source: source, destination: @graph.get_node(current_node_id * -1), probability: @objects[tid].probability, transition: @objects[tid].name)
|
451
|
+
end
|
452
|
+
coverability_helper get_markings, @graph.get_node(current_node_id.abs), added_omega if (current_node_id >= 0 || !omega) && current_node_id != -Float::INFINITY
|
453
|
+
end
|
454
|
+
set_markings markings
|
455
|
+
end
|
456
|
+
end
|
457
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PetriNet::Place < PetriNet::Base
|
4
|
+
# Unique ID
|
5
|
+
attr_reader :id
|
6
|
+
# Needed to sanitize a Petrinet after merging
|
7
|
+
attr_writer :id
|
8
|
+
# Human readable name
|
9
|
+
attr_accessor :name
|
10
|
+
# description
|
11
|
+
attr_accessor :description
|
12
|
+
# Token capacity
|
13
|
+
attr_accessor :capacity
|
14
|
+
# List of input-arcs
|
15
|
+
attr_reader :inputs
|
16
|
+
# List of output-arcs
|
17
|
+
attr_reader :outputs
|
18
|
+
# Current token
|
19
|
+
attr_reader :markings
|
20
|
+
# The net this place belongs to
|
21
|
+
attr_writer :net
|
22
|
+
|
23
|
+
# Initialize a new place. Supports block configuration.
|
24
|
+
def initialize(options = {}, &block)
|
25
|
+
@id = next_object_id
|
26
|
+
@name = (options[:name] || "Place#{@id}")
|
27
|
+
@description = (options[:description] || "Place #{@id}")
|
28
|
+
@capacity = options[:capacity].nil? ? Float::INFINITY : options[:capacity]
|
29
|
+
@inputs = []
|
30
|
+
@outputs = []
|
31
|
+
@markings = []
|
32
|
+
|
33
|
+
yield self unless block.nil?
|
34
|
+
end
|
35
|
+
|
36
|
+
# Add an input arc
|
37
|
+
def add_input(arc)
|
38
|
+
@inputs << arc.id unless arc.nil? || !validate_input(arc)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Add an output arc
|
42
|
+
def add_output(arc)
|
43
|
+
@outputs << arc.id unless arc.nil? || !validate_input(arc)
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_marking(count = 1)
|
47
|
+
if count <= @capacity
|
48
|
+
count.times do
|
49
|
+
@markings << PetriNet::Marking.new
|
50
|
+
end
|
51
|
+
true
|
52
|
+
else
|
53
|
+
raise 'Tried to add more markings than possible'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def set_marking(count)
|
58
|
+
@markings = []
|
59
|
+
add_marking count
|
60
|
+
end
|
61
|
+
|
62
|
+
alias + add_marking
|
63
|
+
|
64
|
+
def remove_marking(count = 1)
|
65
|
+
if @markings.size >= count
|
66
|
+
ret = @markings.pop(count)
|
67
|
+
return ret unless ret.nil?
|
68
|
+
else
|
69
|
+
raise 'Tried to remove more markings that possible'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
alias - remove_marking
|
73
|
+
|
74
|
+
# GraphViz ID
|
75
|
+
def gv_id
|
76
|
+
"P#{@id}"
|
77
|
+
end
|
78
|
+
|
79
|
+
# Validate the setup of this place.
|
80
|
+
def validate
|
81
|
+
return false if @id.nil? || (@id < 0)
|
82
|
+
return false if @name.nil? || (@name.strip.length <= 0)
|
83
|
+
return false if @description.nil? || (@description.strip.length <= 0)
|
84
|
+
return false if @capacity.nil? || (@capacity < -1)
|
85
|
+
|
86
|
+
true
|
87
|
+
end
|
88
|
+
|
89
|
+
def pretransitions
|
90
|
+
raise 'Not part of a net' if @net.nil?
|
91
|
+
|
92
|
+
transitions = []
|
93
|
+
places << inputs.map { |i| @net.objects[i].source }
|
94
|
+
end
|
95
|
+
|
96
|
+
def posttransitions
|
97
|
+
raise 'Not part of a net' if @net.nil?
|
98
|
+
|
99
|
+
outputs.map { |o| @net.objects[o].source }
|
100
|
+
end
|
101
|
+
|
102
|
+
# Stringify this place.
|
103
|
+
def to_s
|
104
|
+
"#{@id}: #{@name} (#{@capacity.nil? ? -1 : 0}) #{'*' * @markings.length}"
|
105
|
+
end
|
106
|
+
|
107
|
+
# GraphViz definition
|
108
|
+
def to_gv
|
109
|
+
"\t#{gv_id} [ label = \"#{@name} #{@markings.size} \" ];\n"
|
110
|
+
end
|
111
|
+
|
112
|
+
def ==(object)
|
113
|
+
return true if name == object.name && description = object.description
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def validate_input(arc)
|
119
|
+
inputs.each do |a|
|
120
|
+
return false if (@net.get_objects[a] <=> arc) == 0
|
121
|
+
end
|
122
|
+
true
|
123
|
+
end
|
124
|
+
|
125
|
+
def validate_output(arc)
|
126
|
+
outputs.each do |a|
|
127
|
+
return false if (@net.get_objects[a] <=> arc) == 0
|
128
|
+
end
|
129
|
+
true
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PetriNet::ReachabilityGraph::Edge < PetriNet::Graph::Edge
|
4
|
+
# Creates an edge for PetriNet::ReachabilityGraph
|
5
|
+
def initialize(graph, options = {}, &block)
|
6
|
+
super(graph, options)
|
7
|
+
yield self unless block.nil?
|
8
|
+
end
|
9
|
+
|
10
|
+
# Validates the data holded by this edge, this will be used while adding the edge to the graph
|
11
|
+
def validate
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'graphviz'
|
4
|
+
|
5
|
+
class PetriNet::ReachabilityGraph < PetriNet::Graph
|
6
|
+
def initialize(net, options = {})
|
7
|
+
options[:type] = 'Reachability'
|
8
|
+
super(net, options)
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_node(node)
|
13
|
+
@nodes.each_value do |n|
|
14
|
+
raise PetriNet::InfiniteReachabilityGraphError if @objects[n] < node
|
15
|
+
rescue ArgumentError
|
16
|
+
# Just an InfiniteNode
|
17
|
+
end
|
18
|
+
super node
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_node!(node)
|
22
|
+
super node
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PetriNet::ReachabilityGraph::Node < PetriNet::Graph::Node
|
4
|
+
def initialize(graph, options = {}, &block)
|
5
|
+
super(graph, options)
|
6
|
+
yield self unless block.nil?
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class PetriNet::ReachabilityGraph::InfinityNode < PetriNet::ReachabilityGraph::Node
|
11
|
+
def initialize(graph)
|
12
|
+
super(graph, markings: [Float::INFINITY])
|
13
|
+
end
|
14
|
+
end
|