y_petri 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 +20 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +31 -0
- data/Rakefile +2 -0
- data/lib/y_petri/demonstrator.rb +164 -0
- data/lib/y_petri/demonstrator_2.rb +176 -0
- data/lib/y_petri/demonstrator_3.rb +150 -0
- data/lib/y_petri/demonstrator_4.rb +217 -0
- data/lib/y_petri/manipulator.rb +598 -0
- data/lib/y_petri/net.rb +458 -0
- data/lib/y_petri/place.rb +189 -0
- data/lib/y_petri/simulation.rb +1313 -0
- data/lib/y_petri/timed_simulation.rb +281 -0
- data/lib/y_petri/transition.rb +921 -0
- data/lib/y_petri/version.rb +3 -0
- data/lib/y_petri/workspace/instance_methods.rb +254 -0
- data/lib/y_petri/workspace/parametrized_subclassing.rb +26 -0
- data/lib/y_petri/workspace.rb +16 -0
- data/lib/y_petri.rb +141 -0
- data/test/simple_manual_examples.rb +28 -0
- data/test/y_petri_graph.png +0 -0
- data/test/y_petri_test.rb +1521 -0
- data/y_petri.gemspec +21 -0
- metadata +112 -0
@@ -0,0 +1,921 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
# Now is a good time to talk about transition classification:
|
4
|
+
#
|
5
|
+
# STOICHIOMETRIC / NON-STOICHIOMETRIC
|
6
|
+
# I. For stoichiometric transitions:
|
7
|
+
# 1. Rate vector is computed as rate * stoichiometry vector, or
|
8
|
+
# 2. Δ vector is computed a action * stoichiometry vector.
|
9
|
+
# II. For non-stoichiometric transitions:
|
10
|
+
# 1. Rate vector is obtained as the rate closure result, or
|
11
|
+
# 2. action vector is obtained as the action closure result.
|
12
|
+
#
|
13
|
+
# Conclusion: stoichiometricity distinguishes *need to multiply the
|
14
|
+
# rate/action closure result by stoichiometry*.
|
15
|
+
#
|
16
|
+
# HAVING / NOT HAVING RATE
|
17
|
+
# I. For transitions with rate, the closure result has to be
|
18
|
+
# multiplied by the time step duration (delta_t) to get action.
|
19
|
+
# II. For rateless transitions, the closure result is used as is.
|
20
|
+
#
|
21
|
+
# Conclusion: has_rate? distinguishes *need to multiply the closure
|
22
|
+
# result by delta time* - differentiability of action by time.
|
23
|
+
#
|
24
|
+
# TIMED / TIMELESS
|
25
|
+
# I. For timed transitions, action is time-dependent. Transitions with
|
26
|
+
# rate are thus always timed. In rateless transitions, timedness means
|
27
|
+
# that the action closure expects time step length (delta_t) as its first
|
28
|
+
# argument - its arity is thus codomain size + 1.
|
29
|
+
# II. For timeless transitions, action is time-independent. Timeless
|
30
|
+
# transitions are necessarily also rateless. Arity of the action closure
|
31
|
+
# is expected to match the domain size.
|
32
|
+
#
|
33
|
+
# Conclusion: Transitions with rate are always timed. In rateless
|
34
|
+
# transitions, timedness distinguishes the need to supply time step
|
35
|
+
# duration as the first argument to the action closure.
|
36
|
+
#
|
37
|
+
# ASSIGNMENT TRANSITIONS
|
38
|
+
# Named argument :assignment_action set to true indicates that the
|
39
|
+
# transitions acts by replacing the object stored as place marking by
|
40
|
+
# the object supplied by the transition. For numeric types, same
|
41
|
+
# effect can be achieved by subtracting the old number from the place
|
42
|
+
# and subsequently adding the new value to it.
|
43
|
+
#
|
44
|
+
module YPetri
|
45
|
+
|
46
|
+
# Represents a Petri net transition. YPetri transitions come in 6
|
47
|
+
# basic types
|
48
|
+
#
|
49
|
+
# === Basic transition types
|
50
|
+
#
|
51
|
+
# * <b>ts</b> – timeless nonstoichiometric
|
52
|
+
# * <b>tS</b> – timeless stoichiometric
|
53
|
+
# * <b>Tsr</b> – timed rateless nonstoichiometric
|
54
|
+
# * <b>TSr</b> – timed rateless stoichiometric
|
55
|
+
# * <b>sR</b> – nonstoichiometric with rate
|
56
|
+
# * <b>SR</b> – stoichiometric with rate
|
57
|
+
#
|
58
|
+
# These 6 kinds of YPetri transitions correspond to the vertices
|
59
|
+
# of a cube with 3 dimensions:
|
60
|
+
#
|
61
|
+
# - stoichiometric (S) / nonstoichiometric (s)
|
62
|
+
# - timed (T) / timeless (t)
|
63
|
+
# - having rate (R) / not having rate (r)
|
64
|
+
#
|
65
|
+
# Since transitions with rate are always timed, and vice-versa, timeless
|
66
|
+
# transitions cannot have rate, there are only 6 permissible combinations,
|
67
|
+
# mentioned above.
|
68
|
+
#
|
69
|
+
# === Domain and codomin
|
70
|
+
#
|
71
|
+
# Each transition has a domain, or 'upstream places': A collection of places
|
72
|
+
# whose marking directly affects the transition's operation. Also, each
|
73
|
+
# transition has a codomain, or 'downstream places': A collection of places,
|
74
|
+
# whose marking is directly affected by the transition's operation.
|
75
|
+
#
|
76
|
+
# === Action and action vector
|
77
|
+
#
|
78
|
+
# Regardless of the type, every transition has <em>action</em>:
|
79
|
+
# A prescription of how the transition changes the marking of its codomain
|
80
|
+
# when it fires. With respect to the transition's codomain, we can also
|
81
|
+
# talk about <em>action vector</em>. For non-stoichiometric transitions,
|
82
|
+
# the action vector is directly the output of the action closure or rate
|
83
|
+
# closure multiplied by Δtime, while for stoichiometric transitions, this
|
84
|
+
# needs to be additionaly multiplied by the transitions stoichiometric
|
85
|
+
# vector. Now we are finally equipped to talk about the exact meaning of
|
86
|
+
# 3 basic transition properties.
|
87
|
+
#
|
88
|
+
# === Meaning of the 3 basic transition properties
|
89
|
+
#
|
90
|
+
# ==== Stoichiometric / non-stoichiometric
|
91
|
+
# * For stoichiometric transitions:
|
92
|
+
# [Rate vector] is computed as rate * stoichiometry vector, or
|
93
|
+
# [Δ vector] is computed a action * stoichiometry vector
|
94
|
+
# * For non-stoichiometric transitions:
|
95
|
+
# [Rate vector] is obtained as the rate closure result, or
|
96
|
+
# [action vector] is obtained as the action closure result.
|
97
|
+
#
|
98
|
+
# Conclusion: stoichiometricity distinguishes <b>need to multiply the
|
99
|
+
# rate/action closure result by stoichiometry</b>.
|
100
|
+
#
|
101
|
+
# ==== Having / not having rate
|
102
|
+
# * For transitions with rate, the closure result has to be
|
103
|
+
# multiplied by the time step duration (Δt) to get the action.
|
104
|
+
# * For rateless transitions, the closure result is used as is.
|
105
|
+
#
|
106
|
+
# Conclusion: has_rate? distinguishes <b>the need to multiply the closure
|
107
|
+
# result by delta time</b> - differentiability of action by time.
|
108
|
+
#
|
109
|
+
# ==== Timed / Timeless
|
110
|
+
# * For timed transitions, action is time-dependent. Transitions with
|
111
|
+
# rate are thus always timed. In rateless transitions, timedness means
|
112
|
+
# that the action closure expects time step length (delta_t) as its first
|
113
|
+
# argument - its arity is thus codomain size + 1.
|
114
|
+
# * For timeless transitions, action is time-independent. Timeless
|
115
|
+
# transitions are necessarily also rateless. Arity of the action closure
|
116
|
+
# is expected to match the domain size.
|
117
|
+
#
|
118
|
+
# Conclusion: Transitions with rate are always timed. In rateless
|
119
|
+
# transitions, timedness distinguishes <b>the need to supply time step
|
120
|
+
# duration as the first argument to the action closure</b>.
|
121
|
+
#
|
122
|
+
# === Other transition types
|
123
|
+
#
|
124
|
+
# ==== Assignment transitions
|
125
|
+
# Named argument :assignment_action set to true indicates that the
|
126
|
+
# transitions acts by replacing the object stored as place marking by
|
127
|
+
# the object supplied by the transition. (Same as in with spreadsheet
|
128
|
+
# functions.) For numeric types, same effect can be achieved by subtracting
|
129
|
+
# the old number from the place and subsequently adding the new value to it.
|
130
|
+
#
|
131
|
+
# ==== Functional / Functionless transitions
|
132
|
+
# Original Petri net definition does not speak about transition "functions",
|
133
|
+
# but it more or less assumes timeless action according to the stoichiometry.
|
134
|
+
# So in YPetri, stoichiometric transitions with no action / rate closure
|
135
|
+
# specified become functionless transitions as meant by Carl Adam Petri.
|
136
|
+
#
|
137
|
+
class Transition
|
138
|
+
include NameMagic
|
139
|
+
|
140
|
+
BASIC_TRANSITION_TYPES = {
|
141
|
+
ts: "timeless nonstoichiometric transition",
|
142
|
+
tS: "timeless stoichiometric transition",
|
143
|
+
Tsr: "timed rateless nonstoichiometric transition",
|
144
|
+
TSr: "timed rateless stoichiometric transition",
|
145
|
+
sR: "nonstoichiometric transition with rate",
|
146
|
+
SR: "stoichiometric transition with rate"
|
147
|
+
}
|
148
|
+
|
149
|
+
# Domain, or 'upstream arcs', is a collection of places, whose marking
|
150
|
+
# directly affects the transition's action.
|
151
|
+
#
|
152
|
+
attr_reader :domain
|
153
|
+
alias :domain_arcs :domain
|
154
|
+
alias :domain_places :domain
|
155
|
+
alias :upstream :domain
|
156
|
+
alias :upstream_arcs :domain
|
157
|
+
alias :upstream_places :domain
|
158
|
+
|
159
|
+
# Names of upstream places.
|
160
|
+
#
|
161
|
+
def domain_pp; domain.map &:name end
|
162
|
+
alias :upstream_pp :domain_pp
|
163
|
+
|
164
|
+
# Names of upstream places as symbols.
|
165
|
+
#
|
166
|
+
def domain_pp_sym; domain_pp.map &:to_sym end
|
167
|
+
alias :upstream_pp_sym :domain_pp_sym
|
168
|
+
alias :domain_ppß :domain_pp_sym
|
169
|
+
alias :ustream_ppß :domain_pp_sym
|
170
|
+
|
171
|
+
# Codomain, 'downstream arcs', or 'action arcs' is a collection of places,
|
172
|
+
# whose marking is directly changed by firing the trinsition.
|
173
|
+
#
|
174
|
+
attr_reader :codomain
|
175
|
+
alias :codomain_arcs :codomain
|
176
|
+
alias :codomain_places :codomain
|
177
|
+
alias :downstream :codomain
|
178
|
+
alias :downstream_arcs :codomain
|
179
|
+
alias :downstream_places :codomain
|
180
|
+
alias :action_arcs :codomain
|
181
|
+
|
182
|
+
# Names of downstream places.
|
183
|
+
#
|
184
|
+
def codomain_pp; codomain.map &:name end
|
185
|
+
alias :downstream_pp :codomain_pp
|
186
|
+
|
187
|
+
# Names of downstream places as symbols.
|
188
|
+
#
|
189
|
+
def codomain_pp_sym; codomain_pp.map &:to_sym end
|
190
|
+
alias :downstream_pp_sym :codomain_pp_sym
|
191
|
+
alias :codomain_ppß :codomain_pp_sym
|
192
|
+
alias :downstream_ppß :codomain_pp_sym
|
193
|
+
|
194
|
+
# Returns the union of action arcs and test arcs.
|
195
|
+
#
|
196
|
+
def arcs; domain | codomain end
|
197
|
+
alias :connectivity :arcs
|
198
|
+
|
199
|
+
# Returns connectivity as names.
|
200
|
+
#
|
201
|
+
def cc; connectivity.map &:name end
|
202
|
+
|
203
|
+
# Returns connectivity as name symbols.
|
204
|
+
#
|
205
|
+
def cc_sym; cc.map &:to_sym end
|
206
|
+
alias :ccß :cc_sym
|
207
|
+
|
208
|
+
# Is the transition stoichiometric?
|
209
|
+
#
|
210
|
+
def stoichiometric?; @stoichiometric end
|
211
|
+
alias :s? :stoichiometric?
|
212
|
+
|
213
|
+
# Is the transition nonstoichiometric? (Opposite of #stoichiometric?)
|
214
|
+
#
|
215
|
+
def nonstoichiometric?; not stoichiometric? end
|
216
|
+
|
217
|
+
# Stoichiometry (implies that the transition is stoichiometric).
|
218
|
+
#
|
219
|
+
attr_reader :stoichiometry
|
220
|
+
|
221
|
+
# Stoichiometry as a hash of pairs:
|
222
|
+
# { codomain_place_instance => stoichiometric_coefficient }
|
223
|
+
#
|
224
|
+
def stoichio; Hash[ codomain.zip( @stoichiometry ) ] end
|
225
|
+
|
226
|
+
# Stoichiometry as a hash of pairs:
|
227
|
+
# { codomain_place_name_symbol => stoichiometric_coefficient }
|
228
|
+
#
|
229
|
+
def s; stoichio.with_keys { |k| k.name.to_sym } end
|
230
|
+
|
231
|
+
# Does the transition have rate?
|
232
|
+
#
|
233
|
+
def has_rate?; @has_rate end
|
234
|
+
|
235
|
+
# Is the transition rateless?
|
236
|
+
#
|
237
|
+
def rateless?; not has_rate? end
|
238
|
+
|
239
|
+
# The term 'flux' (meaning flow) is associated with continuous transitions,
|
240
|
+
# while term 'propensity' is used with discrete stochastic transitions.
|
241
|
+
# By the design of YPetri, distinguishing between discrete and continuous
|
242
|
+
# computation is the responsibility of the simulation method, considering
|
243
|
+
# current marking of the transition's connectivity and quanta of its
|
244
|
+
# codomain. To emphasize unity of 'flux' and 'propensity', term 'rate' is
|
245
|
+
# used to represent both of them. Rate closure input arguments must
|
246
|
+
# correspond to the domain places.
|
247
|
+
#
|
248
|
+
attr_reader :rate_closure
|
249
|
+
alias :rate :rate_closure
|
250
|
+
alias :flux_closure :rate_closure
|
251
|
+
alias :flux :rate_closure
|
252
|
+
alias :propensity_closure :rate_closure
|
253
|
+
alias :propensity :rate_closure
|
254
|
+
|
255
|
+
# For rateless transition, action closure must be present. Action closure
|
256
|
+
# input arguments must correspond to the domain places, and for timed
|
257
|
+
# transitions, the first argument of the action closure must be Δtime.
|
258
|
+
#
|
259
|
+
attr_reader :action_closure
|
260
|
+
alias :action :action_closure
|
261
|
+
|
262
|
+
# Does the transition's action depend on delta time?
|
263
|
+
#
|
264
|
+
def timed?; @timed end
|
265
|
+
|
266
|
+
# Is the transition timeless? (Opposite of #timed?)
|
267
|
+
#
|
268
|
+
def timeless?; not timed? end
|
269
|
+
|
270
|
+
# Is the transition functional?
|
271
|
+
# Explanation: If rate or action closure is supplied, a transition is always
|
272
|
+
# considered 'functional'. Otherwise, it is considered not 'functional'.
|
273
|
+
# Note that even transitions that are not functional still have standard
|
274
|
+
# action acc. to Petri's definition. Also note that a timed transition is
|
275
|
+
# necessarily functional.
|
276
|
+
#
|
277
|
+
def functional?; @functional end
|
278
|
+
|
279
|
+
# Opposite of #functional?
|
280
|
+
#
|
281
|
+
def functionless?; not functional? end
|
282
|
+
|
283
|
+
# Reports transition membership in one of 6 basic types of YPetri transitions:
|
284
|
+
# 1. ts ..... timeless nonstoichiometric
|
285
|
+
# 2. tS ..... timeless stoichiometric
|
286
|
+
# 3. Tsr .... timed rateless nonstoichiometric
|
287
|
+
# 4. TSr .... timed rateless stoichiometric
|
288
|
+
# 5. sR ..... nonstoichiometric with rate
|
289
|
+
# 6. SR ..... stoichiometric with rate
|
290
|
+
#
|
291
|
+
def basic_type
|
292
|
+
if has_rate? then stoichiometric? ? :SR : :sR
|
293
|
+
elsif timed? then stoichiometric? ? :TSr : :Tsr
|
294
|
+
else stoichiometric? ? :tS : :ts end
|
295
|
+
end
|
296
|
+
|
297
|
+
# Reports transition's type (basic type + whether it's an assignment
|
298
|
+
# transition).
|
299
|
+
#
|
300
|
+
def type
|
301
|
+
assignment_action? ? "A(ts)" : basic_type
|
302
|
+
end
|
303
|
+
|
304
|
+
# Is it an assignment transition?
|
305
|
+
#
|
306
|
+
# A transition can be specified to have 'assignment action', in which case
|
307
|
+
# it completely replaces codomain marking with the objects resulting from
|
308
|
+
# the transition's action. Note that for numeric marking, specifying
|
309
|
+
# assignment action is a matter of convenience, not necessity, as it can
|
310
|
+
# be emulated by fully subtracting the present codomain values and adding
|
311
|
+
# the numbers computed by the transition to them. Assignment action flag
|
312
|
+
# is a matter of necessity only when codomain marking involves objects
|
313
|
+
# not supporting subtraction/addition (which is out of the scope of Petri's
|
314
|
+
# original specification anyway.)
|
315
|
+
#
|
316
|
+
def assignment_action?; @assignment_action end
|
317
|
+
alias :assignment? :assignment_action?
|
318
|
+
|
319
|
+
# Is the transition cocked?
|
320
|
+
#
|
321
|
+
# The transition has to be cocked before #fire method can be called
|
322
|
+
# successfully. (Can be overriden using #fire! method.)
|
323
|
+
#
|
324
|
+
def cocked?; @cocked end
|
325
|
+
|
326
|
+
# Opposite of #cocked?
|
327
|
+
#
|
328
|
+
def uncocked?; not cocked? end
|
329
|
+
|
330
|
+
# As you could have noted in the introduction, Transition class encompasses
|
331
|
+
# all different kinds of Petri net transitions. This is considered a good
|
332
|
+
# design pattern for cases like this, but it makes the transition class and
|
333
|
+
# its constructor look a bit complicated. Should you feel that way, please
|
334
|
+
# remember that you only learn one constructor, but can create many kinds
|
335
|
+
# of transition – the computer is doing a lot of work behind the scenes for
|
336
|
+
# you. The type of a transition created depends on the qualities of supplied
|
337
|
+
# arguments. However, you can also explicitly specify what kind of
|
338
|
+
# transition do you want, to exclude any ambiguity.
|
339
|
+
#
|
340
|
+
# Whatever arguments you supply, the constructor will always need a way to
|
341
|
+
# determine domain (upstream arcs) and codomain (downstream arcs) of your
|
342
|
+
# transitions, implicitly or explicitly. Secondly, the constructor must
|
343
|
+
# have a way to determine the transition's action, although there is more
|
344
|
+
# than one way of doing so. So enough talking and onto the examples. We
|
345
|
+
# will imagine having 3 places A, B, C, for which we will create various
|
346
|
+
# transitions:
|
347
|
+
#
|
348
|
+
# ==== Timeless nonstoichiometric (ts) transitions
|
349
|
+
# Action closure has to be supplied, whose return arity correspons to
|
350
|
+
# the codomain size.
|
351
|
+
# <tt>
|
352
|
+
# Transition.new upstream_arcs: [A, C], downstream_arcs: [A, B],
|
353
|
+
# action_closure: proc { |m, x|
|
354
|
+
# if x > 0 then [-(m / 2), (m / 2)]
|
355
|
+
# else [1, 0] end }
|
356
|
+
# </tt>
|
357
|
+
# (This represents a transition connected by arcs to places A, B, C, whose
|
358
|
+
# operation depends on C in such way, that if C.marking is positive,
|
359
|
+
# then half of the marking of A is shifted to B, while if C.marking is
|
360
|
+
# nonpositive, 1 is added to A.)
|
361
|
+
#
|
362
|
+
# ==== Timeless stoichiometric (tS) transitions
|
363
|
+
# Stochiometry has to be supplied, with optional action closure.
|
364
|
+
# Action closure return arity should be 1 (its result will be multiplied
|
365
|
+
# by the stoichiometry vector).
|
366
|
+
#
|
367
|
+
# If no action closure is given, a <em>functionless</em> transition will
|
368
|
+
# be created, whose action closure will be by default 1 * stoichiometry
|
369
|
+
# vector.
|
370
|
+
#
|
371
|
+
# ==== Timed rateless nonstoichiometric (Tsr) transitions
|
372
|
+
# Action closure has to be supplied, whose first argument is Δt, and the
|
373
|
+
# remaining arguments correspond to the domain size. Return arity of this
|
374
|
+
# closure should correspond to the codomain size.
|
375
|
+
#
|
376
|
+
# ==== Timed rateless stoichiometric (TSr) transitions
|
377
|
+
# Action closure has to be supplied, whose first argument is Δt, and the
|
378
|
+
# remaining arguments correspond to the domain size. Return arity of this
|
379
|
+
# closure should be 1 (to be multiplied by the stoichiometry vector).
|
380
|
+
#
|
381
|
+
# ==== Nonstoichiometric transitions with rate (sR)
|
382
|
+
# Rate closure has to be supplied, whose arity should correspond to the
|
383
|
+
# domain size (Δt argument is not needed). Return arity of this closure
|
384
|
+
# should correspond to the codomain size and represents rate of change
|
385
|
+
# contribution for marking of the codomain places.
|
386
|
+
#
|
387
|
+
# ==== Stoichiometric transitions with rate (SR)
|
388
|
+
#
|
389
|
+
# Rate closure and stoichiometry has to be supplied, whose arity should
|
390
|
+
# correspond to the domain size. Return arity of this closure should be 1
|
391
|
+
# (to be multiplied by the stoichiometry vector, as in all stoichiometric
|
392
|
+
# transitions).
|
393
|
+
#
|
394
|
+
# <tt>Transition( stoichiometry: { A: -1, B: 1 },
|
395
|
+
# rate: λ { |a| a * 0.5 } )
|
396
|
+
#
|
397
|
+
def initialize *args
|
398
|
+
# do the big work of checking in the arguments
|
399
|
+
check_in_arguments *args
|
400
|
+
# Inform the relevant places that they have been connected:
|
401
|
+
upstream.each{ |place| place.send :register_downstream_transition, self }
|
402
|
+
downstream.each{ |place| place.send :register_upstream_transition, self }
|
403
|
+
# transitions initialize uncocked:
|
404
|
+
@cocked = false
|
405
|
+
end
|
406
|
+
|
407
|
+
# Marking of the domain places.
|
408
|
+
#
|
409
|
+
def domain_marking
|
410
|
+
domain.map &:marking
|
411
|
+
end
|
412
|
+
|
413
|
+
# Marking of the codomain places.
|
414
|
+
#
|
415
|
+
def codomain_marking
|
416
|
+
codomain.map &:marking
|
417
|
+
end
|
418
|
+
|
419
|
+
# Result of the transition's "function", regardless of the #enabled? status.
|
420
|
+
#
|
421
|
+
def action Δt=nil
|
422
|
+
raise AErr, "Δtime argument required for timed transitions!" if
|
423
|
+
timed? and Δt.nil?
|
424
|
+
# the code here looks awkward, because I was trying to speed it up
|
425
|
+
if has_rate? then
|
426
|
+
if stoichiometric? then
|
427
|
+
rate = rate_closure.( *domain_marking )
|
428
|
+
stoichiometry.map{ |coeff| rate * coeff * Δt }
|
429
|
+
else # assuming correct return value arity from the rate closure:
|
430
|
+
rate_closure.( *domain_marking ).map{ |e| component * Δt }
|
431
|
+
end
|
432
|
+
else # rateless
|
433
|
+
if timed? then
|
434
|
+
if stoichiometric? then
|
435
|
+
rslt = action_closure.( Δt, *domain_marking )
|
436
|
+
stoichiometry.map{ |coeff| rslt * coeff }
|
437
|
+
else
|
438
|
+
action_closure.( Δt, *domain_marking ) # caveat result arity!
|
439
|
+
end
|
440
|
+
else # timeless
|
441
|
+
if stoichiometric? then
|
442
|
+
rslt = action_closure.( *domain_marking )
|
443
|
+
stoichiometry.map{ |coeff| rslt * coeff }
|
444
|
+
else
|
445
|
+
action_closure.( *domain_marking ) # caveat result arity!
|
446
|
+
end
|
447
|
+
end
|
448
|
+
end
|
449
|
+
end # action
|
450
|
+
|
451
|
+
# Zero action
|
452
|
+
#
|
453
|
+
def zero_action
|
454
|
+
codomain.map { 0 }
|
455
|
+
end
|
456
|
+
|
457
|
+
# Changes to the marking of codomain, as they would happen if #fire! was
|
458
|
+
# called right now (ie. honoring #enabled?, but not #cocked? status.
|
459
|
+
#
|
460
|
+
def action_after_feasibility_check( Δt=nil )
|
461
|
+
raise AErr, "Δtime argument required for timed transitions!" if
|
462
|
+
timed? and Δt.nil?
|
463
|
+
act = Array( action Δt )
|
464
|
+
# Assignment actions are always feasible - no need to check:
|
465
|
+
return act if assignment?
|
466
|
+
# check if the marking after the action would still be positive
|
467
|
+
enabled = codomain
|
468
|
+
.zip( act )
|
469
|
+
.all?{ |place, change| place.marking.to_f >= -change.to_f }
|
470
|
+
if enabled then act else
|
471
|
+
raise "firing of #{self}#{ Δt ? ' with Δtime %s' % Δt : '' } " +
|
472
|
+
"would result in negative marking"
|
473
|
+
zero_action
|
474
|
+
end
|
475
|
+
# LATER: This use of #zip here should be avoided for speed
|
476
|
+
end
|
477
|
+
|
478
|
+
# Allows #fire method to succeed. (#fire! disregards cocking.)
|
479
|
+
#
|
480
|
+
def cock; @cocked = true end
|
481
|
+
alias :cock! :cock
|
482
|
+
|
483
|
+
# Uncocks a cocked transition without firing it.
|
484
|
+
#
|
485
|
+
def uncock; @cocked = false end
|
486
|
+
alias :uncock! :uncock
|
487
|
+
|
488
|
+
# If #fire method of a transition applies its action (token adding/taking)
|
489
|
+
# on its domain, depending on codomain marking. Time step is expected as
|
490
|
+
# argument if the transition is timed. Only works if the transition has
|
491
|
+
# been cocked and causes the transition to uncock.
|
492
|
+
#
|
493
|
+
def fire( Δt=nil )
|
494
|
+
raise AErr, "Δtime argument required for timed transitions!" if
|
495
|
+
timed? and Δt.nil?
|
496
|
+
return false unless cocked?
|
497
|
+
uncock
|
498
|
+
fire! Δt
|
499
|
+
return true
|
500
|
+
end
|
501
|
+
|
502
|
+
# #fire! (with bang) fires the transition regardless of cocked status.
|
503
|
+
#
|
504
|
+
def fire!( Δt=nil )
|
505
|
+
raise AErr, "Δtime required for timed transitions!" if timed? && Δt.nil?
|
506
|
+
if assignment_action? then
|
507
|
+
act = Array action( Δt )
|
508
|
+
codomain.each_with_index do |place, i|
|
509
|
+
place.marking = act[i]
|
510
|
+
end
|
511
|
+
else
|
512
|
+
act = action_after_feasibility_check( Δt )
|
513
|
+
codomain.each_with_index do |place, i|
|
514
|
+
place.add act[i]
|
515
|
+
end
|
516
|
+
end
|
517
|
+
return nil
|
518
|
+
end
|
519
|
+
|
520
|
+
# Sanity of execution is ensured by Petri's notion of transitions being
|
521
|
+
# "enabled" if and only if the intended action can immediately take
|
522
|
+
# place without getting places into forbidden state (negative marking).
|
523
|
+
#
|
524
|
+
def enabled?( Δt=nil )
|
525
|
+
raise AErr, "Δtime argument required for timed transitions!" if
|
526
|
+
timed? and Δt.nil?
|
527
|
+
codomain
|
528
|
+
.zip( action Δt )
|
529
|
+
.all? { |place, change| place.marking.to_f >= -change.to_f }
|
530
|
+
end
|
531
|
+
|
532
|
+
# Recursive firing of the upstream net portion (honors #cocked?).
|
533
|
+
#
|
534
|
+
def fire_upstream_recursively
|
535
|
+
return false unless cocked?
|
536
|
+
uncock
|
537
|
+
upstream_places.each &:fire_upstream_recursively
|
538
|
+
fire!
|
539
|
+
return true
|
540
|
+
end
|
541
|
+
|
542
|
+
# Recursive firing of the downstream net portion (honors #cocked?).
|
543
|
+
#
|
544
|
+
def fire_downstream_recursively
|
545
|
+
return false unless cocked?
|
546
|
+
uncock
|
547
|
+
fire!
|
548
|
+
downstream_places.each &:fire_downstream_recursively
|
549
|
+
return true
|
550
|
+
end
|
551
|
+
|
552
|
+
# def lock
|
553
|
+
# # LATER
|
554
|
+
# end
|
555
|
+
# alias :disable! :force_disabled
|
556
|
+
|
557
|
+
# def unlock
|
558
|
+
# # LATER
|
559
|
+
# end
|
560
|
+
# alias :undisable! :remove_force_disabled
|
561
|
+
|
562
|
+
# def force_enabled!( boolean )
|
563
|
+
# # true - the transition is always regarded as enabled
|
564
|
+
# # false - the status is removed
|
565
|
+
# # LATER
|
566
|
+
# end
|
567
|
+
|
568
|
+
# def clamp
|
569
|
+
# # LATER
|
570
|
+
# end
|
571
|
+
|
572
|
+
# def remove_clamp
|
573
|
+
# # LATER
|
574
|
+
# end
|
575
|
+
|
576
|
+
# def reset!
|
577
|
+
# uncock
|
578
|
+
# remove_force_disabled
|
579
|
+
# remove_force_enabled
|
580
|
+
# remove_clamp
|
581
|
+
# return self
|
582
|
+
# end
|
583
|
+
|
584
|
+
# Inspect string for a transition.
|
585
|
+
#
|
586
|
+
def inspect
|
587
|
+
to_s
|
588
|
+
end
|
589
|
+
|
590
|
+
# Conversion to a string.
|
591
|
+
#
|
592
|
+
def to_s
|
593
|
+
"#<Transition: %s >" %
|
594
|
+
"#{name.nil? ? '' : '%s ' % name }(#{basic_type}%s)%s" %
|
595
|
+
[ "#{assignment_action? ? ' Assign.' : ''}",
|
596
|
+
"#{name.nil? ? ' id:%s' % object_id : ''}" ]
|
597
|
+
end
|
598
|
+
|
599
|
+
private
|
600
|
+
|
601
|
+
# **********************************************************************
|
602
|
+
# ARGUMENT CHECK-IN UPON INITIALIZATION
|
603
|
+
# **********************************************************************
|
604
|
+
|
605
|
+
# Checking in the arguments supplied to #initialize looks like a big job.
|
606
|
+
# I won't contest to that, but let us not, that it is basically nothing
|
607
|
+
# else then defining the duck type of the input argument collection.
|
608
|
+
# TypeError is therefore raised if invalid collection has been supplied.
|
609
|
+
#
|
610
|
+
def check_in_arguments *args
|
611
|
+
oo = args.extract_options!
|
612
|
+
oo.may_have :stoichiometry, syn!: [ :stoichio,
|
613
|
+
:s ]
|
614
|
+
oo.may_have :codomain, syn!: [ :codomain_arcs,
|
615
|
+
:codomain_places,
|
616
|
+
:downstream,
|
617
|
+
:downstream_arcs,
|
618
|
+
:downstream_places,
|
619
|
+
:action_arcs ]
|
620
|
+
oo.may_have :domain, syn!: [ :domain_arcs,
|
621
|
+
:domain_places,
|
622
|
+
:upstream,
|
623
|
+
:upstream_arcs,
|
624
|
+
:upstream_places ]
|
625
|
+
oo.may_have :rate, syn!: [ :flux,
|
626
|
+
:propensity,
|
627
|
+
:rate_closure,
|
628
|
+
:flux_closure,
|
629
|
+
:propensity_closure,
|
630
|
+
:Φ,
|
631
|
+
:φ ]
|
632
|
+
oo.may_have :action, syn!: :action_closure
|
633
|
+
oo.may_have :timed
|
634
|
+
|
635
|
+
# was the rate was given?
|
636
|
+
@has_rate = oo.has? :rate
|
637
|
+
|
638
|
+
# is the transition stoichiometric (S) or nonstoichiometric (s)?
|
639
|
+
@stoichiometric = oo.has? :stoichiometry
|
640
|
+
|
641
|
+
# downstream description arguments: codomain, stoichiometry (if S)
|
642
|
+
if stoichiometric? then
|
643
|
+
@codomain, @stoichiometry = check_in_downstream_description_for_S( oo )
|
644
|
+
else # s transitions have no stoichiometry
|
645
|
+
@codomain = check_in_downstream_description_for_s( oo )
|
646
|
+
end
|
647
|
+
|
648
|
+
# check in domain first, :missing symbol may appear
|
649
|
+
@domain = check_in_domain( oo )
|
650
|
+
|
651
|
+
# upstream description arguments; also takes care of :missing domain
|
652
|
+
if has_rate? then
|
653
|
+
@domain, @rate_closure, @timed, @functional =
|
654
|
+
check_in_upstream_description_for_R( oo )
|
655
|
+
else
|
656
|
+
@domain, @action_closure, @timed, @functional =
|
657
|
+
check_in_upstream_description_for_r( oo )
|
658
|
+
end
|
659
|
+
|
660
|
+
# optional assignment action:
|
661
|
+
@assignment_action = check_in_assignment_action( oo )
|
662
|
+
end # def check_in_arguments
|
663
|
+
|
664
|
+
# Makes sure that supplied collection consists only of appropriate places.
|
665
|
+
# Second optional argument customizes the error message.
|
666
|
+
#
|
667
|
+
def sanitize_place_collection place_collection, what_is_collection=nil
|
668
|
+
c = what_is_collection ? what_is_collection.capitalize : "Collection"
|
669
|
+
Array( place_collection ).map do |pl_id|
|
670
|
+
begin
|
671
|
+
place( pl_id )
|
672
|
+
rescue NameError
|
673
|
+
raise TErr, "#{c} member #{pl_id} does not specify a valid place!"
|
674
|
+
end
|
675
|
+
end.aT what_is_collection, "not contain duplicate places" do |collection|
|
676
|
+
collection == collection.uniq
|
677
|
+
end
|
678
|
+
end
|
679
|
+
|
680
|
+
# Private method, part of #initialize argument checking-in.
|
681
|
+
#
|
682
|
+
def check_in_domain( oo )
|
683
|
+
if oo.has? :domain then
|
684
|
+
sanitize_place_collection( oo[:domain], "supplied domain" )
|
685
|
+
else
|
686
|
+
if stoichiometric? then
|
687
|
+
# take arcs with non-positive stoichiometry coefficients
|
688
|
+
Hash[ [ @codomain, @stoichiometry ].transpose ]
|
689
|
+
.delete_if{ |place, coeff| coeff > 0 }.keys
|
690
|
+
else
|
691
|
+
:missing
|
692
|
+
# Barring the caller's error, missing domain can mean:
|
693
|
+
# 1. empty domain
|
694
|
+
# 2. domain == codomain
|
695
|
+
# This will be figured later by rate/action closure arity
|
696
|
+
end
|
697
|
+
end
|
698
|
+
end
|
699
|
+
|
700
|
+
def check_in_upstream_description_for_R( oo )
|
701
|
+
_domain = domain # this method may modify domain
|
702
|
+
# check against colliding :action argument
|
703
|
+
raise TErr, "Rate & action are mutually exclusive!" if oo.has? :action
|
704
|
+
# lets figure the rate closure
|
705
|
+
rate_λ = case rate_arg = oo[:rate]
|
706
|
+
when Proc then # We received the closure directly,
|
707
|
+
# but we've to be concerned about missing domain.
|
708
|
+
if domain == :missing then # we've to figure user's intent
|
709
|
+
_domain = if rate_arg.arity == 0 then
|
710
|
+
[] # user meant empty domain
|
711
|
+
else
|
712
|
+
codomain # user meant domain same as codomain
|
713
|
+
end
|
714
|
+
else # domain not missing
|
715
|
+
raise TErr, "Rate closure arity (#{rate_arg.arity}) " +
|
716
|
+
"greater than domain size (#{domain.size})!" unless
|
717
|
+
rate_arg.arity.abs <= domain.size
|
718
|
+
end
|
719
|
+
rate_arg
|
720
|
+
else # We received something else,
|
721
|
+
# we must make assumption user's intent.
|
722
|
+
if stoichiometric? then # user's intent was mass action
|
723
|
+
raise TErr, "When a number is supplied as rate, domain " +
|
724
|
+
"must not be given!" if oo.has? :domain
|
725
|
+
construct_standard_mass_action( rate_arg )
|
726
|
+
else # user's intent was constant closure
|
727
|
+
raise TErr, "When rate is a number and no stoichiometry " +
|
728
|
+
"is supplied, codomain size must be 1!" unless
|
729
|
+
codomain.size == 1
|
730
|
+
# Missing domain is OK here,
|
731
|
+
_domain = [] if domain == :missing
|
732
|
+
# but if it was supplied explicitly, it must be empty.
|
733
|
+
raise TErr, "Rate is a number, but non-empty domain was " +
|
734
|
+
"supplied!" unless domain.empty? if oo.has?( :domain )
|
735
|
+
lambda { rate_arg }
|
736
|
+
end
|
737
|
+
end
|
738
|
+
# R transitions are implicitly timed
|
739
|
+
_timed = true
|
740
|
+
# check against colliding :timed argument
|
741
|
+
oo[:timed].tE :timed, "not be false if rate given" if oo.has? :timed
|
742
|
+
# R transitions are implicitly functional
|
743
|
+
_functional = true
|
744
|
+
return _domain, rate_λ, _timed, _functional
|
745
|
+
end
|
746
|
+
|
747
|
+
def check_in_upstream_description_for_r( oo )
|
748
|
+
_domain = domain # this method may modify domain
|
749
|
+
_functional = true
|
750
|
+
# was action closure was given explicitly?
|
751
|
+
if oo.has? :action then
|
752
|
+
action_λ = oo[:action].aT_is_a Proc, "supplied action named argument"
|
753
|
+
if oo.has? :timed then
|
754
|
+
_timed = oo[:timed]
|
755
|
+
# Time to worry about the domain_missing
|
756
|
+
if domain == :missing then
|
757
|
+
# figure user's intent from closure arity
|
758
|
+
_domain = if action_λ.arity == ( _timed ? 1 : 0 ) then
|
759
|
+
[] # user meant empty domain
|
760
|
+
else
|
761
|
+
codomain # user meant domain same as codomain
|
762
|
+
end
|
763
|
+
else # domain not missing
|
764
|
+
raise TErr, "Rate closure arity (#{rate_arg.arity}) > domain " +
|
765
|
+
"size (#{domain.size})!" if action_λ.arity.abs > domain.size
|
766
|
+
end
|
767
|
+
else # :timed argument not supplied
|
768
|
+
if domain == :missing then
|
769
|
+
# If no domain was supplied, there is no way to reasonably figure
|
770
|
+
# out the user's intent, except when arity is 0:
|
771
|
+
_domain = case action_λ.arity
|
772
|
+
when 0 then
|
773
|
+
_timed = false
|
774
|
+
[] # empty domain is implied
|
775
|
+
else # no deduction of user intent possible
|
776
|
+
raise AErr, "Too much ambiguity: Neither domain nor " +
|
777
|
+
"timedness of the rateless transition was specified."
|
778
|
+
end
|
779
|
+
else # domain not missing
|
780
|
+
# Even if the user did not bother to inform us explicitly about
|
781
|
+
# timedness, we can use closure arity as a clue. If it equals the
|
782
|
+
# domain size, leaving no room for Δtime argument, the user intent
|
783
|
+
# was to create timeless transition. If it equals domain size + 1,
|
784
|
+
# theu user intended to create a timed transition.
|
785
|
+
_timed = case action_λ.arity
|
786
|
+
when domain.size then false
|
787
|
+
when domain.size + 1 then true
|
788
|
+
else # no deduction of user intent possible
|
789
|
+
raise AErr, "Timedness was not specified, and the " +
|
790
|
+
"arity of the action supplied action closure " +
|
791
|
+
"(#{action_λ.arity}) does not give clear hint on it."
|
792
|
+
end
|
793
|
+
end
|
794
|
+
end
|
795
|
+
else # rateless cases with no action closure specified
|
796
|
+
# Assumption must be made on transition's action. In particular,
|
797
|
+
# lambda { 1 } action closure will be assumed,
|
798
|
+
action_λ = lambda { 1 }
|
799
|
+
# and it will be required that the transition be stoichiometric and
|
800
|
+
# timeless. Domain will thus be required empty.
|
801
|
+
raise AErr, "Stoichiometry is compulsory, if rate/action was " +
|
802
|
+
"not supplied." unless stoichiometric?
|
803
|
+
# With this, we can drop worries about missing domain.
|
804
|
+
raise AErr, "When no rate/action is supplied, the transition can't " +
|
805
|
+
"be declared timed." if oo[:timed] if oo.has? :timed
|
806
|
+
_timed = false
|
807
|
+
_domain = []
|
808
|
+
_functional = false # the transition is considered functionless
|
809
|
+
end
|
810
|
+
return _domain, action_λ, _timed, _functional
|
811
|
+
end
|
812
|
+
|
813
|
+
def construct_standard_mass_action( num )
|
814
|
+
# assume standard mass-action law
|
815
|
+
nonpositive_coeffs = stoichiometry.select { |coeff| coeff <= 0 }
|
816
|
+
# the closure takes markings of the domain as its arguments
|
817
|
+
lambda { |*markings|
|
818
|
+
nonpositive_coeffs.size.times.reduce num do |acc, i|
|
819
|
+
marking, coeff = markings[ i ], nonpositive_coeffs[ i ]
|
820
|
+
# Stoichiometry coefficients equal to zero are taken to indicate
|
821
|
+
# plain factors, assuming that if these places were not involved
|
822
|
+
# in the transition at all, the user would not be mentioning them.
|
823
|
+
case coeff
|
824
|
+
when 0, -1 then marking * acc
|
825
|
+
else marking ** -coeff end
|
826
|
+
end
|
827
|
+
}
|
828
|
+
end
|
829
|
+
|
830
|
+
# Private method, checking in downstream specification from the argument
|
831
|
+
# field for stoichiometric transition.
|
832
|
+
#
|
833
|
+
def check_in_downstream_description_for_S( oo )
|
834
|
+
codomain, stoichio =
|
835
|
+
case oo[:stoichiometry]
|
836
|
+
when Hash then
|
837
|
+
# contains pairs { codomain place => stoichiometry coefficient }
|
838
|
+
raise AErr, "With hash-type stoichiometry, :codomain named " +
|
839
|
+
"argument must not be supplied." if oo.has? :codomain
|
840
|
+
oo[:stoichiometry].each_with_object [[], []] do |pair, memo|
|
841
|
+
codomain_place, stoichio_coeff = pair
|
842
|
+
memo[0] << codomain_place
|
843
|
+
memo[1] << stoichio_coeff
|
844
|
+
end
|
845
|
+
else
|
846
|
+
# array of stoichiometry coefficients
|
847
|
+
raise AErr, "With array-type stoichiometry, :codomain named " +
|
848
|
+
"argument must be supplied." unless oo.has? :codomain
|
849
|
+
[ oo[:codomain], Array( oo[:stoichiometry] ) ]
|
850
|
+
end
|
851
|
+
# enforce that stoichiometry is a collection of numbers
|
852
|
+
return sanitize_place_collection( codomain, "supplied codomain" ),
|
853
|
+
stoichio.aT_all_numeric( "supplied stoichiometry" )
|
854
|
+
end
|
855
|
+
|
856
|
+
# Private method, checking in downstream specification from the argument
|
857
|
+
# field for nonstoichiometric transition.
|
858
|
+
#
|
859
|
+
def check_in_downstream_description_for_s( oo )
|
860
|
+
# codomain must be explicitly given - no way around it:
|
861
|
+
raise AErr, "For non-stoichiometric transitions, :codomain named " +
|
862
|
+
"argument is compulsory." unless oo.has? :codomain
|
863
|
+
return sanitize_place_collection( oo[:codomain], "supplied codomain" )
|
864
|
+
end
|
865
|
+
|
866
|
+
# Private method, part of #initialize argument checking-in.
|
867
|
+
#
|
868
|
+
def check_in_assignment_action( oo )
|
869
|
+
if oo.has? :assignment_action, syn!: [ :assignment, :assign, :A ] then
|
870
|
+
if timed? then
|
871
|
+
msg = "Timed transitions may not have assignment action!"
|
872
|
+
raise TypeError, msg if oo[:assignment_action]
|
873
|
+
false
|
874
|
+
else # timeless transitions are eligible for assignment action
|
875
|
+
oo[:assignment_action]
|
876
|
+
end
|
877
|
+
else # if assignment action is not specified, false is
|
878
|
+
false
|
879
|
+
end
|
880
|
+
end
|
881
|
+
|
882
|
+
# Place class pertinent herein. Provided for the purpose of parametrized
|
883
|
+
# subclassing; expected to be overriden in the subclasses.
|
884
|
+
#
|
885
|
+
def Place
|
886
|
+
::YPetri::Place
|
887
|
+
end
|
888
|
+
|
889
|
+
# Transition class pertinent herein. Provided for the purpose of
|
890
|
+
# parametrized subclassing; expected to be overriden in the subclasses.
|
891
|
+
#
|
892
|
+
def Transition
|
893
|
+
::YPetri::Transition
|
894
|
+
end
|
895
|
+
|
896
|
+
# Net class pertinent herein. Provided for the purpose of parametrized
|
897
|
+
# subclassing; expected to be overriden in the subclasses.
|
898
|
+
#
|
899
|
+
def Net
|
900
|
+
::YPetri::Net
|
901
|
+
end
|
902
|
+
|
903
|
+
# Presents Place instance specified by the argument.
|
904
|
+
#
|
905
|
+
def place instance_identifier
|
906
|
+
Place().instance( instance_identifier )
|
907
|
+
end
|
908
|
+
|
909
|
+
# Presents Transition instance specified by the argument.
|
910
|
+
#
|
911
|
+
def transition instance_identifier
|
912
|
+
Transition().instance( instance_identifier )
|
913
|
+
end
|
914
|
+
|
915
|
+
# Presents Net instance specified by the argument.
|
916
|
+
#
|
917
|
+
def net instance_identifier
|
918
|
+
Net().instance( instance_identifier )
|
919
|
+
end
|
920
|
+
end # class Transition
|
921
|
+
end # module YPetri
|