petrinet 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,158 @@
1
+ require 'set'
2
+ require_relative 'pnml_builder'
3
+ require_relative 'graphviz_builder'
4
+ require_relative 'animated_gif_builder'
5
+
6
+ module Petrinet
7
+ # Represents a Petri Net in a particular state. Instances of this class are immutable. The following methods return
8
+ # new instances:
9
+ #
10
+ # * mark
11
+ # * fire
12
+ #
13
+ # The internal representation uses a VASS - https://en.wikipedia.org/wiki/Vector_addition_system
14
+ # A good explanation of how this works with Petri Nets is here: https://github.com/bitwrap/bitwrap-io/blob/master/whitepaper.md
15
+ #
16
+ class Net
17
+ def self.from_pnml(xml)
18
+ builder = PnmlBuilder.new(xml)
19
+ builder.net
20
+ end
21
+
22
+ def self.build(&proc)
23
+ builder = Builder.new
24
+ proc.call(builder)
25
+ builder.net
26
+ end
27
+
28
+ attr_reader :prefire_transition_name
29
+
30
+ def initialize(state_vector, place_index_by_place_name, transition_vectors_by_transition_name, prefire_transition_name = nil)
31
+ @state_vector = state_vector
32
+ @place_index_by_place_name = place_index_by_place_name
33
+ @transition_vectors_by_transition_name = transition_vectors_by_transition_name
34
+ @prefire_transition_name = prefire_transition_name
35
+ freeze
36
+ end
37
+
38
+ # Marks the petri net and returns a new instance
39
+ def mark(marking)
40
+ new_state_vector = Array.new(@state_vector.size, 0)
41
+ marking.each do |place_name, token_count|
42
+ index = @place_index_by_place_name[place_name]
43
+ new_state_vector[index] = token_count
44
+ end
45
+ self.class.new(new_state_vector, @place_index_by_place_name, @transition_vectors_by_transition_name)
46
+ end
47
+
48
+ def fire(transition_name)
49
+ new_state_vector = new_state_vector(transition_name)
50
+ self.class.new(new_state_vector, @place_index_by_place_name, @transition_vectors_by_transition_name)
51
+ end
52
+
53
+ def prefire(transition_name)
54
+ self.class.new(@state_vector, @place_index_by_place_name, @transition_vectors_by_transition_name, transition_name)
55
+ end
56
+
57
+ def fireable?(transition_name)
58
+ begin
59
+ new_state_vector(transition_name)
60
+ true
61
+ rescue
62
+ false
63
+ end
64
+ end
65
+
66
+ def fireable
67
+ result = Set.new
68
+ @transition_vectors_by_transition_name.keys.each do |transition_name|
69
+ begin
70
+ fire(transition_name)
71
+ result.add(transition_name)
72
+ rescue => ignore
73
+ # It wasn't fireable - ignore it
74
+ end
75
+ end
76
+ result
77
+ end
78
+
79
+ def to_svg
80
+ builder = GraphvizBuilder.new(self, @transition_vectors_by_transition_name, @place_index_by_place_name.invert, @state_vector)
81
+ builder.svg
82
+ end
83
+
84
+ def to_animated_gif(transition_names, gif_path)
85
+ builder = AnimatedGifBuilder.new(self)
86
+ builder.write(transition_names, gif_path)
87
+ end
88
+
89
+ private
90
+
91
+ def new_state_vector(transition_name)
92
+ transition_vectors = @transition_vectors_by_transition_name[transition_name]
93
+ raise "Unknown transition: #{transition_name}. Known transitions: #{@transition_vectors_by_transition_name.keys}" if transition_vectors.nil?
94
+ transition_vectors.reduce(@state_vector) do |state_vector, transition_vector|
95
+ v = state_vector.zip(transition_vector).map { |s, t| s + t }
96
+ raise "Cannot fire: #{transition_name}" unless valid?(v)
97
+ v
98
+ end
99
+ end
100
+
101
+ def valid?(state_vector)
102
+ !(state_vector.any? { |s| s.negative? })
103
+ end
104
+
105
+ class Builder
106
+ def initialize
107
+ @place_names = Set.new
108
+ @transition_by_name = Hash.new
109
+ @state_vector = []
110
+ end
111
+
112
+ def transition(transition_name, arcs)
113
+ take_place_names = [arcs[:take]].flatten
114
+ give_place_names = [arcs[:give]].flatten
115
+ @place_names.merge(take_place_names)
116
+ @place_names.merge(give_place_names)
117
+ @transition_by_name[transition_name] = Transition.new(take_place_names, give_place_names)
118
+ end
119
+
120
+ def net
121
+ place_index_by_place_name = Hash.new do |h, k|
122
+ index = h.size
123
+ @state_vector[index] = 0
124
+ h[k] = index
125
+ end
126
+
127
+ transition_vectors_by_transition_name_pairs = @transition_by_name.map do |transition_name, transition|
128
+ [transition_name, transition.to_vectors(@place_names.size, place_index_by_place_name)]
129
+ end
130
+ transition_vectors_by_transition_name = Hash[transition_vectors_by_transition_name_pairs]
131
+
132
+ Net.new(@state_vector.freeze, place_index_by_place_name.freeze, transition_vectors_by_transition_name.freeze)
133
+ end
134
+
135
+ class Transition
136
+ def initialize(take_place_names, give_place_names)
137
+ @take_place_names = take_place_names
138
+ @give_place_names = give_place_names
139
+ end
140
+
141
+ def to_vectors(size, place_index_by_place_name)
142
+ take_vector = Array.new(size, 0)
143
+ @take_place_names.each do |take_place_name|
144
+ index = place_index_by_place_name[take_place_name]
145
+ take_vector[index] -= 1
146
+ end
147
+
148
+ give_vector = Array.new(size, 0)
149
+ @give_place_names.each do |give_place_name|
150
+ index = place_index_by_place_name[give_place_name]
151
+ give_vector[index] += 1
152
+ end
153
+ [take_vector, give_vector]
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,71 @@
1
+ require 'nokogiri'
2
+
3
+ module Petrinet
4
+ class PnmlBuilder
5
+ def initialize(xml)
6
+ @xml = xml
7
+ end
8
+
9
+ def net
10
+ doc = Nokogiri::XML(@xml)
11
+
12
+ transition_by_id = {}
13
+ place_by_id = {}
14
+ edges = []
15
+
16
+ transitions = Hash.new {|h, k| h[k] = {take: [], give: []}}
17
+ markings = Hash.new
18
+
19
+ # https://github.com/bitwrap/bitwrap-io/blob/master/bitwrap_io/machine/pnml.py#L216
20
+
21
+ doc.xpath('//place').each do |place_node|
22
+ place_id = place_node[:id].to_sym
23
+ initial_marking_nodes = place_node.xpath('initialMarking/value/text()')
24
+ initial_marking = initial_marking_nodes.empty? ? 0 : initial_marking_nodes[0].text.split(',')[1].to_i
25
+
26
+ # place = Place.new(initial_marking)
27
+ # place_by_id[place_id] = place
28
+
29
+ # TODO: Remove
30
+ markings[place_id] = initial_marking
31
+ end
32
+
33
+ doc.xpath('//transition').each do |transition_node|
34
+ transition_id = transition_node[:id].to_sym
35
+
36
+ # transition = Transition.new
37
+ # transition_by_id[transition_id] = transition
38
+
39
+ # TODO: Remove
40
+ transitions[transition_id]
41
+ end
42
+
43
+ doc.xpath('//arc').each do |arc_node|
44
+ source_id = arc_node[:source].to_sym
45
+ target_id = arc_node[:target].to_sym
46
+
47
+ # edge = Edge.new(source_id, target_id)
48
+ # edges.push(edge)
49
+
50
+ if transitions.has_key?(source_id)
51
+ transition_name = source_id
52
+ place_name = target_id
53
+ transitions[transition_name][:give] << place_name
54
+ elsif transitions.has_key?(target_id)
55
+ transition_name = target_id
56
+ place_name = source_id
57
+ transitions[transition_name][:take] << place_name
58
+ else
59
+ raise "Unknown transition name in one of: #{source_id} -> #{target_id}"
60
+ end
61
+ end
62
+
63
+ net = Net.build do |b|
64
+ transitions.each do |transition_name, places|
65
+ b.transition(transition_name, take: places[:take], give: places[:give])
66
+ end
67
+ end
68
+ net.mark(markings)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,3 @@
1
+ module Petrinet
2
+ VERSION = "0.1.0"
3
+ end
data/lib/petrinet.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "petrinet/version"
2
+ require "petrinet/net"
3
+ require "petrinet/marking_transition_script"
4
+
5
+ module Petrinet
6
+ class Error < StandardError; end
7
+ # Your code goes here...
8
+ end
data/petrinet.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ lib = File.expand_path("lib", __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "petrinet/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "petrinet"
7
+ spec.version = Petrinet::VERSION
8
+ spec.authors = ["Aslak Hellesøy"]
9
+ spec.email = ["aslak.hellesoy@smartbear.com"]
10
+
11
+ spec.summary = %q{Petri Net simulator}
12
+ spec.description = spec.description
13
+ spec.homepage = "https://github.com/cucumber/petrinet"
14
+ spec.license = "MIT"
15
+
16
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
17
+
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ spec.metadata["source_code_uri"] = "#{spec.homepage}.git"
20
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
21
+
22
+ # Specify which files should be added to the gem when it is released.
23
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
25
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ end
27
+ spec.bindir = "exe"
28
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ["lib"]
30
+
31
+ spec.add_dependency "nokogiri", "~> 1.10.4"
32
+
33
+ spec.add_development_dependency "bundler", "~> 2.0"
34
+ spec.add_development_dependency "rake", "~> 10.0"
35
+ spec.add_development_dependency "rspec", "~> 3.0"
36
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: petrinet
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Aslak Hellesøy
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-10-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.10.4
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.10.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ description: ''
70
+ email:
71
+ - aslak.hellesoy@smartbear.com
72
+ executables:
73
+ - petrinet
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".travis.yml"
80
+ - Gemfile
81
+ - LICENSE.txt
82
+ - Makefile
83
+ - README.md
84
+ - Rakefile
85
+ - examples/cucumber-protocol/cucumber-protocol.xml
86
+ - examples/cucumber-protocol/transition_sample_1.gif
87
+ - examples/cucumber-protocol/transition_sample_1.txt
88
+ - examples/voting/voting.xml
89
+ - examples/x-ray-machine/v1-problem.gif
90
+ - examples/x-ray-machine/v1-problem.txt
91
+ - examples/x-ray-machine/v2-fixed.gif
92
+ - examples/x-ray-machine/v2-fixed.txt
93
+ - examples/x-ray-machine/x-ray-machine-1.xml
94
+ - examples/x-ray-machine/x-ray-machine-2.xml
95
+ - exe/petrinet
96
+ - lib/petrinet.rb
97
+ - lib/petrinet/animated_gif_builder.rb
98
+ - lib/petrinet/graphviz_builder.rb
99
+ - lib/petrinet/marking_transition_script.rb
100
+ - lib/petrinet/net.rb
101
+ - lib/petrinet/pnml_builder.rb
102
+ - lib/petrinet/version.rb
103
+ - petrinet.gemspec
104
+ homepage: https://github.com/cucumber/petrinet
105
+ licenses:
106
+ - MIT
107
+ metadata:
108
+ allowed_push_host: https://rubygems.org
109
+ homepage_uri: https://github.com/cucumber/petrinet
110
+ source_code_uri: https://github.com/cucumber/petrinet.git
111
+ changelog_uri: https://github.com/cucumber/petrinet/blob/master/CHANGELOG.md
112
+ post_install_message:
113
+ rdoc_options: []
114
+ require_paths:
115
+ - lib
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ requirements: []
127
+ rubygems_version: 3.0.3
128
+ signing_key:
129
+ specification_version: 4
130
+ summary: Petri Net simulator
131
+ test_files: []