fast-tcpn 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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/README.md ADDED
@@ -0,0 +1,453 @@
1
+ FastTCPN (fast-tcpn) library technical documentation
2
+ ========
3
+
4
+ Fast simulation tool for Timed Colored Petri Nets (TCPN)
5
+ described using convenient DSL and imperative programming
6
+ language (Ruby).
7
+
8
+ This tool provides library to represent TCPN model and exposing
9
+ require API. It also provides DSL that facilitates creating the
10
+ TCPN model.
11
+
12
+ TCPN DSL
13
+ --------
14
+
15
+ DSL is meant to enable convenient creation of the TCPN model. It
16
+ can be interpreted from a file or directly from a block in Ruby
17
+ program.
18
+
19
+ TCPN described using the DSL consists of at least one page, thus
20
+ the description must start with `page` statement. Inside page,
21
+ timed and not timed places can be defined using `timed_place` and
22
+ `place` statements. Objects returned by these statements
23
+ represent places. Transitions are created using `transition`
24
+ statement. For each transition one defines its input and output
25
+ places.
26
+
27
+ ### Pages
28
+
29
+ Pages of TCPN have a name and their content is defined by block
30
+ of code:
31
+
32
+ page "My first model" do
33
+ # put your model here
34
+ end
35
+
36
+ Page names are used in error messages to facilitate location of
37
+ problems.
38
+
39
+ ### Places
40
+
41
+ Places of TCPN are identified by their names. Each place can
42
+ appear on numerous pages. For the sake of efficiency, the tool
43
+ provides two statements defining places. If tokens in a place
44
+ should not consider time, one should use normal `place`
45
+ statement. If time is important and tokens should be delayed in a
46
+ place if their timestamp requires, one should create such place
47
+ using `timed_place` statement. Simulation with not timed places
48
+ is faster, especially for significant number of tokens, thus one
49
+ should use not timed places wherever it is possible.
50
+
51
+ Marking implemented in this tool use hashes to efficiently store,
52
+ match and remove tokens from places. However, defining proper
53
+ criterions to index tokens in places is responsibility of the
54
+ person creating model. Thus defining places, one can pass
55
+ additional parameter defining keys that will be used to quickly
56
+ locate tokens in this particular place. For instance if one knows
57
+ that tokens in place called `:process` will be used in two
58
+ transitions, first of the need to match process tokens using
59
+ their `name` and second using value returned by `valid?` method
60
+ of token value, these two values can be used to index tokens in
61
+ this place. This place would be created using e.g.
62
+
63
+ place :process, name: :name, valid: :valid?
64
+
65
+ The first value of pair is the name of the criterion (key),
66
+ second is the method that will be called on token's value, to
67
+ obtain actual value used while indexing. Tokens from the above
68
+ defined place will be be easily accessible using key `:name` and
69
+ values returned by their method `name` and using key `:valid` and
70
+ values returned by their method `:valid?`.
71
+
72
+ If the method called on token value needs parameter, then the value of
73
+ in the hash may be an array: [ :method_name, method, parameters ], e.g.:
74
+
75
+ place :process, name: :name, valid: :valid?, owner: [ :get_owner, "Jack" ]
76
+
77
+ If a place exists on different pages with different keys, the
78
+ keys from different place definitions will be merged.
79
+ This method of token indexing is used in both: timed and not
80
+ timed places.
81
+
82
+
83
+ ### Transitions
84
+
85
+ Transitions are defined using `transition` statement and
86
+ identified by name. They must exist on at most one page of the
87
+ model. Block passed to transition defines its input and output
88
+ places.
89
+
90
+ #### Input places
91
+
92
+ This example defines transition with one input place and no
93
+ output places. Variable `process` passed to the `input` statement
94
+ must be a `place` object returned by `place` or `timed_place`
95
+ statement. There are no input arc inscriptions. Read description
96
+ of `sentry` to see how tokens from input places are used.
97
+
98
+ transition "work" do
99
+ input process
100
+ end
101
+
102
+ #### Output places
103
+
104
+ Output places of transition are defined using its `output`
105
+ statement. This statement gets `place` object (returned by `place`
106
+ or `timed_place` statement) and a block of code. The block states
107
+ for output arc expression and is used to decide what token should
108
+ be put in the output place, for specified binding and simulation clock.
109
+
110
+ The block passed to `output` statement gets two parameters:
111
+ +binding+ and +clock+. The +binding+ is a hash with names of
112
+ input places as keys and tokens that will be removed from these
113
+ places as values. Currently only single tokens are removed. Value
114
+ returned by the block will be treated as value of token that
115
+ should be put in output place. Or if correct token object is
116
+ returned it will be put it the place.
117
+
118
+ If output place is timed, it is possible to set both: value nad
119
+ timestamp of the output token. It can be done by returning hash:
120
+
121
+ { val: token_value, ts: token_timestamp }
122
+
123
+ If input place is also timed and only timestamp of token should
124
+ be changed when putting it in output place, this can be done
125
+ using `with_timestamp` method:
126
+
127
+ binding[:cpu].with_timestamp + 100
128
+
129
+ To return more then one token to a place just return an Array --
130
+ all definitions or tokens from the Array will be put into the
131
+ place's marking.
132
+
133
+ Complete definition of two places and transition with one input
134
+ and one output place can be defined as follows:
135
+
136
+ process = place :process, { name: :name, valid: :valid? }
137
+ done = place :done
138
+
139
+ transition "work" do
140
+ input process
141
+ output done do
142
+ binding[:process].name + "_done"
143
+ end
144
+ end
145
+
146
+ When fired it will get a process token from the place called
147
+ `:process` and put in place called `:done` a token with value
148
+ being a string being the process name with string `"_done"`
149
+ appended.
150
+
151
+
152
+ #### Sentries (variation of guards)
153
+
154
+ For the sake of efficiency, this tool does not implement
155
+ traditional guard, understood as a method that gets a binding and
156
+ returns true or false. For TCPNs described using imperative
157
+ language, this approach requires analysis of whole Cartesian
158
+ product of input markings.
159
+
160
+ Instead a `sentry` can be defined for a transition, that lets the
161
+ person that create TCPN model implement significantly faster
162
+ solution for the same problem. The sentry is a block of code that
163
+ receives markings of subsequent places and is supposed to
164
+ generate a list of valid bindings. It should do it in the most
165
+ efficient way, that is best known to the model creator, as he is
166
+ the person that knows all specifics of modeled reality and token
167
+ values used. The tool for each place provides access to its
168
+ marking in random order, to ensure fairness, consequently the
169
+ only concern of the person implementing sentry is to correctly
170
+ match tokens. If keys used to index tokens in input places are
171
+ defined correctly, one can immediately access list of tokens
172
+ matching selected criterion and iterate.
173
+
174
+ The block defining `sentry` takes three arguments:
175
+ * `marking_for` -- a hash with place names as keys and place
176
+ markings as values
177
+ * `clock` -- current value of simulation clock
178
+ * `result` -- variable used to collect valid bindings.
179
+ Using information from `marking_for` and `clock` parameters, the
180
+ block is supposed to push subsequent valid bindings to the
181
+ `result` variable using `<<` operator. Each valid binding must be
182
+ of the form of Hash with input place names as keys and token
183
+ as values:
184
+
185
+ result << { process: token1, cpu: cpu5 }
186
+
187
+ To get more then one token to fire a transition, you can put
188
+ array of tokens as value in the mapping passed to the `result`
189
+ like this:
190
+
191
+ result << { process: [ token1, token2 ], cpu: cpu5 }
192
+
193
+ In current implementation of the tool, only the first mapping
194
+ passed to the `result` will be used and the sentry block will not
195
+ be subsequently evaluated. It is however better to implement it
196
+ as iteration over all possible valid bindings for possible future
197
+ uses.
198
+
199
+ Example model with one transition that is supposed to match
200
+ process tokens with correct CPUs, pass process names to `done`
201
+ place and return delayed CPUs to their place will look as
202
+ follows.
203
+
204
+
205
+ page "Example model" do
206
+ p1 = place :process, { name: :name }
207
+ cpu = timed_place :cpu, { process: :process }
208
+ p2 = place :done
209
+
210
+ transition 'run' do
211
+ input p1
212
+ input cpu
213
+ output p2 do |binding|
214
+ binding[:process].value.name.to_s + "_done"
215
+ end
216
+ output cpu do |binding, clock|
217
+ binding[:cpu].with_timestamp clock + 100
218
+ end
219
+
220
+ sentry do |marking_for, clock, result|
221
+ marking_for[:process].each do |p|
222
+ marking_for[:cpu].each(:process, p.value.name) do |c|
223
+ result << { process: p, cpu: c }
224
+ end
225
+ end
226
+ end
227
+ end
228
+ end
229
+
230
+ Place `:process` uses key `:name` as an index, it is not however
231
+ used in this model. As cpus are matched to processes using value
232
+ returned by `cpu.process` method, the CPUs are indexed by this
233
+ value. This allows in `sentry` iteration over cpu tokens that
234
+ match process name selected before:
235
+
236
+ marking_for[:cpu].each(:process, p.value.name) do |c|
237
+
238
+ In this case the first matching pair is returned as no additional
239
+ conditions should be met.
240
+
241
+ If we know that every process from `:process` place can be
242
+ used to fire transition and only correct CPU must be matched, we
243
+ start iteration from processes. If we started from CPUs, we could
244
+ find some CPUs without matching processes and the sentry would be
245
+ slower. This reasoning is however responsibility of model
246
+ creator, as it strongly depends on specifics of the model (one
247
+ should know that every process has at least one CPU, but some
248
+ CPUs may match processes that were already served).
249
+
250
+ ### Pages in pages in pages ...
251
+
252
+ One page can contain not only places and transitions, but also
253
+ definitions of subsequent pages.
254
+
255
+ ### Subpages from files
256
+
257
+ It is possible to load a subpages from separate files using
258
+ `sub_page` statement:
259
+
260
+ page "Test model" do
261
+ sub_page 'network.rb'
262
+ sub_page 'cpus.rb'
263
+ end
264
+
265
+ A subpage can also contain subpages loaded from another files.
266
+ Paths used to locate files mentioned in subpages are interpreted
267
+ as relative to location of currently interpreted file.
268
+
269
+ Using TCPN model
270
+ ----------------
271
+
272
+ ### Loading model
273
+
274
+ The model described by the DSL can be saved in separate file and
275
+ loaded using `TCPN.read` method:
276
+
277
+ tcpn = TCPN.read 'model/example.rb'
278
+
279
+ If the model contains subpages in separate files, they will be
280
+ loaded from paths relative to the location of the `example.rb`
281
+ file.
282
+
283
+ One can also embed the model directly in Ruby program and
284
+ interpret using `TCPN.model method`
285
+
286
+ tcpn = TCPN.model do
287
+ page "Example model" do
288
+ # places and transitions here...
289
+ end
290
+ end
291
+
292
+ Model objects returned by these methods can be used to start
293
+ simulation and define callbacks.
294
+
295
+
296
+ ### Place Marking
297
+
298
+ After the model is created, place marking can be set. It can be
299
+ done using one of the following methods.
300
+
301
+ * Find place by name and add tokens to it
302
+
303
+ tcpn.find_place(:process).add a_process_object
304
+ tcpn.find_place(:process).add array_of_process_objects
305
+
306
+ * Add marking for specified place of the TCPN (old API, derived
307
+ from tcpn gem)
308
+
309
+ tcpn.add_marking_for(:process, a_process_object)
310
+
311
+ To check marking, use one of the following methods:
312
+
313
+ * Find place by name and iterate over its marking:
314
+
315
+ tcpn.find_place(:process).marking.each
316
+
317
+ * Get marking of specified place of TCPN (old API, derived
318
+ from tcpn gem)
319
+
320
+ tcpn.marking_for(:process).each
321
+
322
+ More details on methods of the model can be found in [API doc for
323
+ TCPN class](link:FastTCPN/TCPN.html). More details on methods of
324
+ the place objects can be found in [API doc for Place
325
+ class](link:FastTCPN/Place.html).
326
+
327
+ ### Simulation
328
+
329
+ Simulation of the model can be started using `sim` method.
330
+
331
+ tcpn.sim
332
+
333
+ This method will return when simulation is finished.
334
+
335
+ Simulation finishes whe there is no more transitions to fire even after
336
+ advancing simulation clock. It can also be stopped manually by calling
337
+ `#stop` method:
338
+
339
+ tcpn.stop
340
+
341
+ This method can be used e.g. in callbacks (see below).
342
+
343
+ After simulation has finished, it is possible to check if it was
344
+ finished manually using `#stopped?` method.
345
+
346
+ if tcpn.stopped?
347
+ puts "Simulation was stopped manually."
348
+ else
349
+ puts "Simulation has finished. No more transitions to fire."
350
+ end
351
+
352
+ ### Callbacks
353
+
354
+ Convenient way to obtain results of simulation is provided by
355
+ callbacks. They can be defined
356
+
357
+ * for transitions (before and after firing)
358
+ * for places (when adding and when removing tokens)
359
+ * for clock (before and after changes)
360
+
361
+ Callbacks are defined using `TCPN#cb_for` method. First parameter
362
+ of the method defines if the callback concerns transitions
363
+ (`:transition`), places (`:place`) or clock (`:clock`). Second
364
+ optional parameter defines when callback should be fired:
365
+
366
+ * for transition it can be either `:before` or `:after`
367
+ * for place it can be `:add` or `:remove`
368
+ * for clock it can be either `:before` or `:after`
369
+
370
+ If this parameter is omitted, callback will be fired in both cases.
371
+
372
+ The block passed to `#cb_for` gets two parameters: first is value
373
+ of the tag defining when callback is fired (`:before` or `:after`
374
+ transition is fired, while `:add`ing or `:remove`ing tokens from
375
+ places). Second parameter is event object holding details of the
376
+ event. It is:
377
+
378
+ * for transition callback -- Transition::Event object with
379
+ fields:
380
+ * `transition` -- name of transition being fired
381
+ * `binding` -- token binding used to fire (`place => token(s)`
382
+ Hash)
383
+ * `clock` -- current clock value
384
+ * `tcpn` -- TCPN object representing network being run
385
+ * for place callback -- Place::Event
386
+ * `place` -- name of place being changed
387
+ * `tokens` -- list of tokens added ore removed
388
+ * `clock` -- current simulation clock (only for timed places,
389
+ for not timed this is `nil`
390
+ * `tcpn` -- TCPN object representing network being run
391
+ * for clock callback -- TCPN::ClockEvent object
392
+ * `clock` -- current value of simulation clock
393
+ * `previous_clock` -- previous value of simulation clock (for
394
+ `:before` this one is `nil`)
395
+ * `tcpn` -- TCPN object representing network being run
396
+
397
+ ## Known Errors and Pitfalls
398
+
399
+ ### Arrays and Hashes as token values
400
+
401
+ Arrays and Hashes are used to describe tokens (their list, values and
402
+ timestamps). Therefore passing an Array or a Hash that itself should be
403
+ a token value will not work. If you need to do it, either encapsulate
404
+ the Array or the Hash into your own class or just create an empty class
405
+ that extends Array or Hash -- this should work too.
406
+
407
+ ### Cloning problems
408
+
409
+ Due to nature of TCPN based on functional paradigm TCPN tokens
410
+ are immutable. To achieve this in Ruby (and other imperative
411
+ languages cloning token values is crucial for correct behavior of
412
+ TCPN simulation.
413
+
414
+ Unfortunately, some Ruby objects cannot be cloned. One example is
415
+ Enumerator that was already started (#next was called at least
416
+ once). If you put such value in a token, simulation will fail, as
417
+ the token will not be cloned. Use your own implementation of
418
+ enumeration instead. Please report any other built-in classes
419
+ that cause simulation problems.
420
+
421
+
422
+ ### TODO
423
+
424
+ * Possible optimization can be achieved by implementing
425
+ clone-on-write instead of eager cloning every time when a token
426
+ is passed to user-provided code. We can use
427
+ https://github.com/dkubb/ice_nine to deeple freeze token
428
+ objects, catch exception indicating that a modification was
429
+ tried, clone object and redo the modification on the clone. In
430
+ short. It is not easy and there is no guarantee that overhead
431
+ of the solution won't level gains resulting from lazy cloning.
432
+ * Add traditional guard to enable quick prototyping. This however
433
+ requires a king of input arc inscriptions, as currently this is
434
+ implemented in sentry and will not have its place in the
435
+ traditional guard. Maybe we could allow traditional guards only
436
+ for transitions that remove single tokens? They will not be
437
+ recommended way of implementing this anyway.
438
+ * Currently, HashMarking uses own implementation of
439
+ `lazy_shuffle` to provide tokens in random order. This is meant
440
+ to be possibly fast for small number of tokens yielded. It
441
+ requires benchmarking for cases when most or all tokens from
442
+ marking should be yielded and probably should be reworked for
443
+ these cases. I expect it to be a problem for cases when there
444
+ are tokens in input places, but they do not meed requirements
445
+ and transition cannot be fired -- then probably all tokens will
446
+ be checked by sentry of this transition.
447
+
448
+
449
+
450
+
451
+ # Copyright
452
+
453
+ Copyright (c) 2014-2018 Wojciech Rząsa. See LICENSE.txt for further details. Contact me if interested in different license conditions.