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/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.