fast-tcpn 0.0.5

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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.lock +40 -0
  6. data/LICENSE.txt +674 -0
  7. data/README.md +453 -0
  8. data/fast-tcpn.gemspec +40 -0
  9. data/fast-tcpn.rb +192 -0
  10. data/lib/fast-tcpn.rb +25 -0
  11. data/lib/fast-tcpn/clone.rb +7 -0
  12. data/lib/fast-tcpn/clone/using_code_from_stack.rb +50 -0
  13. data/lib/fast-tcpn/clone/using_deep_clone.rb +10 -0
  14. data/lib/fast-tcpn/clone/using_deep_dive.rb +25 -0
  15. data/lib/fast-tcpn/clone/using_marshal.rb +8 -0
  16. data/lib/fast-tcpn/dsl.rb +177 -0
  17. data/lib/fast-tcpn/hash_marking.rb +220 -0
  18. data/lib/fast-tcpn/place.rb +70 -0
  19. data/lib/fast-tcpn/tcpn.rb +288 -0
  20. data/lib/fast-tcpn/timed_hash_marking.rb +87 -0
  21. data/lib/fast-tcpn/timed_place.rb +44 -0
  22. data/lib/fast-tcpn/timed_token.rb +27 -0
  23. data/lib/fast-tcpn/token.rb +30 -0
  24. data/lib/fast-tcpn/transition.rb +224 -0
  25. data/lib/fast-tcpn/version.rb +3 -0
  26. data/spec/callbacks_spec.rb +164 -0
  27. data/spec/dsl/page_spec.rb +195 -0
  28. data/spec/dsl/transition_spec.rb +41 -0
  29. data/spec/hash_marking_spec.rb +9 -0
  30. data/spec/place_spec.rb +10 -0
  31. data/spec/spec_helper.rb +101 -0
  32. data/spec/support/hash_marking_shared.rb +274 -0
  33. data/spec/support/place_shared.rb +66 -0
  34. data/spec/support/token_shared.rb +27 -0
  35. data/spec/support/uses_temp_files.rb +31 -0
  36. data/spec/tcpn_binding_spec.rb +54 -0
  37. data/spec/tcpn_sim_spec.rb +323 -0
  38. data/spec/tcpn_spec.rb +150 -0
  39. data/spec/timed_hash_marking_spec.rb +132 -0
  40. data/spec/timed_place_spec.rb +38 -0
  41. data/spec/timed_token_spec.rb +50 -0
  42. data/spec/token_spec.rb +13 -0
  43. data/spec/transition_spec.rb +236 -0
  44. metadata +156 -0
data/fast-tcpn.gemspec ADDED
@@ -0,0 +1,40 @@
1
+ # Used this istruction to create the gem:
2
+ # http://guides.rubygems.org/make-your-own-gem/
3
+ # some things below were based on docile gem.
4
+
5
+ $:.push File.expand_path('../lib', __FILE__)
6
+ require 'fast-tcpn/version'
7
+
8
+ Gem::Specification.new do |s|
9
+ s.name = 'fast-tcpn'
10
+ s.version = FastTCPN::VERSION
11
+ s.authors = ['Wojciech Rząsa']
12
+ s.email = %w(me@wojciechrzasa.pl)
13
+ s.homepage = 'https://github.com/wrzasa/fast-tcpn'
14
+ s.summary = 'FastTCPN is Ruby based modeling and simulation tool for simulation of TCPN with convenient DSL.'
15
+ s.description = 'You can model your Timed Colored Petri Net in Ruby using convenient DSL and simulate it quite efficiently.'
16
+ s.license = 'GPL-3.0'
17
+
18
+ s.platform = 'ruby'
19
+ s.required_ruby_version = '~> 2.0'
20
+
21
+ # s.rubyforge_project = ''
22
+
23
+ s.files = `git ls-files -z`.split("\x0")
24
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
25
+ s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
26
+ s.require_paths = %w(lib)
27
+
28
+ s.add_runtime_dependency 'docile', '~> 1.1'
29
+
30
+ # Running rspec tests from rake
31
+ s.add_development_dependency 'rspec', '~> 3.1'
32
+ s.add_development_dependency 'simplecov', '~> 0'
33
+
34
+ s.extra_rdoc_files << 'README.md'
35
+ s.rdoc_options << '--main' << 'README.md'
36
+ s.rdoc_options << '--title' << 'fast-tcpn -- Fast TCPN modeling and simulation tool'
37
+ s.rdoc_options << '--line-numbers'
38
+ s.rdoc_options << '-A'
39
+ s.rdoc_options << '-x coverage'
40
+ end
data/fast-tcpn.rb ADDED
@@ -0,0 +1,192 @@
1
+ # WRz 2014-11-14
2
+ #
3
+ # This is a proof of concept implementation of
4
+ # fast TCPN simulator.
5
+ # Main concept if that guard is not a function
6
+ # receiving binding and answering if it is valid or not.
7
+ # Instead guard gets list of all input tokens from input
8
+ # places and returns valid bindngs. If programmer is a fool
9
+ # and guard is naive, it will be slow as it was so far. But
10
+ # generally it is possible to generate possible bindings
11
+ # using Hashes in linear time (about n*k where k is number of
12
+ # input places and n is number of tokens in each place) instead
13
+ # of n**k as in case of analysing cartesian product for traditional
14
+ # boolean guard.
15
+ #
16
+ # Since what we use instead of guard now is not precisely
17
+ # what Jensen had in mind, we call it sentry, not to confuse
18
+ # anyone.
19
+
20
+ #
21
+ # Note on cloning.
22
+ #
23
+ # Branch clone_selected_tokens assumes that only tokens
24
+ # used by the user are cloned. Here we clone whole marking
25
+ # before it can be used in guard or inscription. For well
26
+ # prepared guard, when possible small number of tokens is
27
+ # taken from marking it is faster to clone individual tokens
28
+ # (4 sec instead of 6.9 sec). But for worse guard it is faster
29
+ # to clone whole marking at the begining, then many individual
30
+ # tokens (14 sec. instead of 6.9 sec). The better guard in this
31
+ # example is when we put all processes in a Hash and try to match
32
+ # a CPU, worse guard is when we put all CPUs in a Hash and try to
33
+ # match a process. We have 10 times more CPUs the processes in this
34
+ # example, and putting a token into a Hash requires cloning it.
35
+ #
36
+ # The above times were obtained for 1000 procs, 10 cpus for each and
37
+ # ruby_deep_clone library.
38
+ #
39
+ # This library might also be worth checking:
40
+ # https://github.com/flajann2/deep_dive/blob/master/lib/deep_dive/deep_dive.rb
41
+ #
42
+ #
43
+ # THIS IS IMPLEMENTED with selective token cloning
44
+ # Possible optimization.
45
+ # If we could define how a place stores its marking, it would make
46
+ # firing transition much faster. Ther would be no need to rewrite
47
+ # tokens from array to other structure, e.g Hash. We could e.g. define
48
+ # that processes should be stored in a Hash keyed by process name and
49
+ # thus make all guards matching by process name much faster! How to
50
+ # do this to make it sufficiently general? Different (custom)
51
+ # implementations of Marking class?
52
+ #
53
+ # HashMarking -- when creating Place define token keys that will be used
54
+ # to search tokens in different transitions, these keys will be passed to
55
+ # HashMarking object created in the Place. The HashMarking object maintains
56
+ # a Hash of tokens for each of the keys defined when creating the Place.
57
+ # Every token in the Place is put in each Hash udenr different keys.
58
+ # HashMarking exposes method named after the keys, that find token in
59
+ # appropriate Hash using given key. This way we will have possibility
60
+ # to quickly find tokens using require criterions. Adding a token to marking
61
+ # requires adding to each Hash (an probably an Array that probably should
62
+ # also be maintaned). Deleting requires removal from every Hash (but using
63
+ # the Hash'es key, thus quickly). It will be a little slower, but adding
64
+ # and deleting is much less often then finding tokens while trying to fire
65
+ # a transition. Memory oveshead will not be significant, since the Hashes
66
+ # will store only reference to a single copy of the object. The keys may
67
+ # not uniquely identify objects, so the Hashes should store arrays of
68
+ # objects matching given key. Iterators should be exposed for these arrays
69
+ # and for the whole marking. The iterators should shuffle the arrays before
70
+ # starting iteration and clone yielded objects -- anyway we assume that
71
+ # shuffling will let one take first object returned by iterator, so cloning
72
+ # will be rare.
73
+ #
74
+ # Thus we will be able to fire transition not in linear time, but in O(1)
75
+ # time at the expense of not significant memory overhead and insignificant
76
+ # complication of add/delete token operations for marking!
77
+ #
78
+ #
79
+ #
80
+ # (***) Note on efficiency (find this mark in code).
81
+ #
82
+ # calling
83
+ # marking_hash[:process].each
84
+ # without additional parameters results in need to do
85
+ # @global_list.keys in HashMarking and to generate an
86
+ # awfully large array. This is major cause that is blocking
87
+ # subsequent speedup of simulation. Unfortunately this
88
+ # array is required to enable shuffling of elements to
89
+ # ensure fair treatment of TCPN conflicts.
90
+ #
91
+ # THE BELOW IS NOT TRUE -- tested it!
92
+ # Similar situation will occcur in case of places that
93
+ # store a lot of tokens under one key -- there we also use
94
+ # Hash with tokens as keys and generate token list using
95
+ # Hash#keys method. Adding and removing tokens is faster
96
+ # in this case, especially for the large token lists.
97
+ #
98
+ #
99
+ # Not timed places causes simulator to be faster! Reason is
100
+ # that their tokens need not be considered when looking for
101
+ # next time to which simulator clock should be advanced to
102
+ # enable a transition. Thus if you have a place with a lot
103
+ # of tokens, you can gain significant speed up by making
104
+ # this place not timed (if you can).
105
+
106
+ require 'benchmark'
107
+ require 'deep_clone'
108
+ require 'fast-tcpn'
109
+
110
+ require 'ruby-prof'
111
+
112
+
113
+ AppProcess = Struct.new(:name)
114
+ CPU = Struct.new(:name, :process)
115
+
116
+
117
+ profile = false
118
+ timed = true
119
+
120
+ tcpn = FastTCPN.model do
121
+
122
+ page "Benchmark model" do
123
+ if timed
124
+ puts "Timed places"
125
+ p1 = timed_place :process, { name: :name }
126
+ cpu = timed_place :cpu, { process: :process }
127
+ p2 = timed_place :done
128
+ else
129
+ puts "Not timed places"
130
+ p1 = place :process, { name: :name }
131
+ cpu = place :cpu, { process: :process }
132
+ p2 = place :done
133
+ end
134
+
135
+ transition 'run' do
136
+ input p1
137
+ input cpu
138
+ output p2 do |binding|
139
+ binding[:process].value.name.to_s + "_done"
140
+ end
141
+ output cpu do |binding|
142
+ binding[:cpu]
143
+ end
144
+
145
+ sentry do |marking_for, clock, result|
146
+ # (***) see note on efficiency above
147
+ marking_for[:process].each do |p|
148
+ marking_for[:cpu].each(:process, p.value.name) do |c|
149
+ result << { process: p, cpu: c }
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+
157
+
158
+ p1 = tcpn.find_place(:process)
159
+ p2 = tcpn.find_place(:done)
160
+ cpu = tcpn.find_place(:cpu)
161
+
162
+ 10_000.times do |p|
163
+ p1.add AppProcess.new(p)
164
+ 10.times.map { |c| cpu.add CPU.new("CPU#{c}_#{p}", p) }
165
+ end
166
+
167
+ puts p1.marking.size
168
+ puts p2.marking.size
169
+ puts cpu.marking.size
170
+
171
+ RubyProf.start if profile
172
+
173
+ Benchmark.bm do |x|
174
+ x.report do
175
+ tcpn.sim
176
+ # {} while t.fire
177
+ end
178
+ end
179
+
180
+
181
+ if profile
182
+ result = RubyProf.stop
183
+ # Print a flat profile to text
184
+ printer = RubyProf::FlatPrinter.new(result)
185
+ #printer = RubyProf::FlatPrinterWithLineNumbers.new(result)
186
+ #printer = RubyProf::GraphHtmlPrinter.new(result)
187
+ printer.print(STDOUT)
188
+ end
189
+
190
+ puts p1.marking.size
191
+ puts p2.marking.size
192
+ puts cpu.marking.size
data/lib/fast-tcpn.rb ADDED
@@ -0,0 +1,25 @@
1
+ require 'fast-tcpn/version'
2
+ require 'fast-tcpn/place'
3
+ require 'fast-tcpn/timed_place'
4
+ require 'fast-tcpn/transition'
5
+ require 'fast-tcpn/clone'
6
+ require 'fast-tcpn/hash_marking'
7
+ require 'fast-tcpn/timed_hash_marking'
8
+ require 'fast-tcpn/token'
9
+ require 'fast-tcpn/timed_token'
10
+ require 'fast-tcpn/tcpn'
11
+ require 'fast-tcpn/dsl'
12
+
13
+ module FastTCPN
14
+ @@debug = false
15
+
16
+ # Check debugging of FastTCPN -- full backtraces from simulator
17
+ def self.debug
18
+ @@debug
19
+ end
20
+
21
+ # Turn on/off debugging of FastTCPN -- full backtraces from simulator
22
+ def self.debug=(d)
23
+ @@debug = d
24
+ end
25
+ end
@@ -0,0 +1,7 @@
1
+ # This file should require one selected
2
+ # implementation of deep-cloning from clone/ dir.
3
+
4
+ #require 'fast-tcpn/clone/using_deep_clone'
5
+ #require 'fast-tcpn/clone/using_deep_dive'
6
+ #require 'fast-tcpn/clone/using_marshal'
7
+ require 'fast-tcpn/clone/using_code_from_stack'
@@ -0,0 +1,50 @@
1
+ module FastTCPN
2
+ DontCloneClasses = [ Numeric, Symbol, TrueClass, FalseClass, NilClass ]
3
+ module Clone
4
+ # :nodoc:
5
+ def clone(token)
6
+ token.deep_clone
7
+ end
8
+ end
9
+ end
10
+
11
+ # http://stackoverflow.com/questions/8206523/how-to-create-a-deep-copy-of-an-object-in-ruby
12
+ class Object
13
+ def deep_clone
14
+ return @deep_cloning_obj if @deep_cloning
15
+ return @deep_cloning_obj if frozen?
16
+ @deep_cloning_obj = clone
17
+ @deep_cloning_obj.instance_variables.each do |var|
18
+ val = @deep_cloning_obj.instance_variable_get(var)
19
+ begin
20
+ @deep_cloning = true
21
+ val = val.deep_clone
22
+ # silent rescue is never a good idea...
23
+ #rescue TypeError
24
+ # next
25
+ ensure
26
+ # better not to litter original object with
27
+ # temporary instance variables
28
+ #@deep_cloning = false
29
+ remove_instance_variable :@deep_cloning
30
+ end
31
+ @deep_cloning_obj.instance_variable_set(var, val)
32
+ end
33
+ deep_cloning_obj = @deep_cloning_obj
34
+ @deep_cloning_obj = nil
35
+ # better not to litter original object with
36
+ # temporary instance variables
37
+ remove_instance_variable :@deep_cloning_obj
38
+ deep_cloning_obj
39
+ end
40
+ end
41
+
42
+ FastTCPN::DontCloneClasses.each do |klazz|
43
+ klazz.class_eval do
44
+ def deep_clone
45
+ self
46
+ end
47
+ end
48
+ end
49
+
50
+
@@ -0,0 +1,10 @@
1
+ require 'deep_clone'
2
+
3
+ module FastTCPN
4
+ module Clone
5
+ # :nodoc:
6
+ def clone(token)
7
+ DeepClone.clone token
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,25 @@
1
+ require 'deep_dive'
2
+
3
+ [ Fixnum, Symbol, TrueClass, FalseClass ].each do |klazz|
4
+ klazz.class_eval do
5
+ def clone
6
+ self
7
+ end
8
+ end
9
+ end
10
+
11
+ class Object
12
+ include DeepDive
13
+ exclude do |sym, obj|
14
+ obj.kind_of? Symbol
15
+ end
16
+ end
17
+
18
+ module FastTCPN
19
+ module Clone
20
+ # :nodoc:
21
+ def clone(token)
22
+ token.dclone
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,8 @@
1
+ module FastTCPN
2
+ module Clone
3
+ # :nodoc:
4
+ def clone(token)
5
+ Marshal.load Marshal.dump token
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,177 @@
1
+ require 'docile'
2
+
3
+ module FastTCPN
4
+ # Allows to create new TCPN model by interpreting passed block of code.
5
+ # If you read this code from a file, pass it's name as a parameter, it
6
+ # will be used in exceptions.
7
+ def self.model(file = nil, &block)
8
+ tcpn = TCPN.new
9
+ load_model tcpn, file, &block
10
+ tcpn
11
+ end
12
+
13
+ # Read TCPN model from a +file+.
14
+ def self.read(file)
15
+ block = instance_eval "proc { #{File.read file} }", file
16
+ model file, &block
17
+ end
18
+
19
+ # For interal use. Use #read od #model.
20
+ def self.load_model(tcpn, file, &block)
21
+ Docile.dsl_eval(DSL::TCPNDSL.new(tcpn, file), &block)
22
+ end
23
+
24
+ # This module implements DSL to easily create TCPN models.
25
+ # The model consists of pages that have places and transitions.
26
+ # Places are repsesented by their names and the same place can
27
+ # appear on numerous pages. Every page can consist of subsequent
28
+ # pages. Pages can be loaded to the model from other files.
29
+ #
30
+ # DSL models can be loaded from external files using FastTCPN.read method
31
+ # or interpreted directly from core using FastTCPN.model method. Both methods
32
+ # return created model as TCPN object. You can then use TCPN class API to set
33
+ # markings, test markings, define callbacks and run simulation.
34
+ #
35
+ # Example:
36
+ #
37
+ # File: model/top.rb
38
+ # page "top" do
39
+ # sub_page "process.rb"
40
+ # end
41
+ #
42
+ # File: model/process.rb
43
+ # page "process" do
44
+ # process = timed_place :process, { name: :name }
45
+ # done = timed_place :done { name: :name }
46
+ #
47
+ # transition "work" do
48
+ # input process
49
+ # output done do |binding, clock|
50
+ # binding[:process].with_time clock + 100
51
+ # end
52
+ # end
53
+ # end
54
+ #
55
+ # Calling TCPN.read 'model/top.rb' will load whole model and return as TCPN object.
56
+ module DSL
57
+
58
+ # Represents and encapsulates all errors that will occur while
59
+ # running DSL.
60
+ class DSLError < RuntimeError
61
+ attr_reader :cause, :page, :files
62
+
63
+ def initialize(cause, page, file = nil)
64
+ super cause
65
+ @cause, @page = cause, page
66
+ if @cause.respond_to? :full_backtrace
67
+ set_backtrace @cause.full_backtrace
68
+ else
69
+ set_backtrace @cause.backtrace
70
+ end
71
+ @files = []
72
+ @files += @cause.files if @cause.respond_to? :files
73
+ @files << file
74
+ end
75
+
76
+ def inspect
77
+ "<#{self.class} #{@cause.inspect} on TCPN page: `#{@page}`>"
78
+ end
79
+
80
+ def message
81
+ "#{@cause.message} on TCPN page: `#{@page}`"
82
+ end
83
+
84
+ alias full_backtrace backtrace
85
+
86
+ def backtrace
87
+ return full_backtrace if @files.empty?
88
+ full_backtrace.select do |b|
89
+ not @files.select { |f| b =~ /#{f}/ }.empty?
90
+ end
91
+ end
92
+ end
93
+
94
+ class TCPNDSL
95
+ def initialize(tcpn, file)
96
+ @tcpn = tcpn
97
+ @file = file
98
+ end
99
+
100
+ # Define a page of TCPN model
101
+ def page(name, &block)
102
+ Docile.dsl_eval(PageDSL.new(@tcpn, self, @file), &block)
103
+ rescue StandardError => e
104
+ raise DSLError.new e, name, @file
105
+ rescue SyntaxError => e
106
+ raise DSLError.new e, name, @file
107
+ end
108
+ end
109
+
110
+ class PageDSL
111
+ def initialize(tcpn, dsl, file)
112
+ @tcpn = tcpn
113
+ @dsl = dsl
114
+ @file = file
115
+ end
116
+
117
+ # Create and return a new place (not timed). If a place with this
118
+ # name exists somewhere in the model (e.g. on other pages), and object
119
+ # representing exisiting place will be returned. Keys of both keys will
120
+ # be merged.
121
+ def place(name, keys = {})
122
+ @tcpn.place name, keys
123
+ end
124
+
125
+ # Create and return a new timed place.
126
+ def timed_place(name, keys = {})
127
+ @tcpn.timed_place name, keys
128
+ end
129
+
130
+ # Create and return new transition for the TCPN. Block
131
+ # defines transitions inputs, outputs and sentry.
132
+ def transition(name, &block)
133
+ transition = @tcpn.transition name
134
+ Docile.dsl_eval(TransitionDSL.new(transition), &block)
135
+ transition
136
+ end
137
+
138
+ # define new TCPN (sub) page on this page.
139
+ def page(name, &block)
140
+ @dsl.page name, &block
141
+ end
142
+
143
+ # load TCPN sub page from a file. File location is relative to
144
+ # the location of currently interpreted file (if known)
145
+ def sub_page(file)
146
+ file = File.expand_path(file, File.dirname(@file)) unless @file.nil?
147
+ block = instance_eval "proc { #{File.read file} }", file
148
+ FastTCPN.load_model @tcpn, file, &block
149
+ end
150
+ end
151
+
152
+ class TransitionDSL
153
+ def initialize(transition)
154
+ @transition = transition
155
+ end
156
+
157
+ # Defines input place for this transition. +place+ must an
158
+ # object returned by +place+ ot +timed_place+ statement.
159
+ def input(place)
160
+ @transition.input place
161
+ end
162
+
163
+ # Defines output place for this transition. +place+ must an
164
+ # object returned by +place+ ot +timed_place+ statement.
165
+ #
166
+ def output(place, &block)
167
+ @transition.output place, &block
168
+ end
169
+
170
+ # Defines sentry for this transition.
171
+ def sentry(&block)
172
+ @transition.sentry &block
173
+ end
174
+ end
175
+
176
+ end
177
+ end