y_petri 2.0.3 → 2.0.7
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 +4 -4
- data/lib/y_petri/dependency_injection.rb +45 -0
- data/lib/y_petri/manipulator/petri_net_related_methods.rb +26 -7
- data/lib/y_petri/manipulator/simulation_related_methods.rb +4 -4
- data/lib/y_petri/net.rb +30 -21
- data/lib/y_petri/place/arcs.rb +96 -0
- data/lib/y_petri/place/guard.rb +122 -0
- data/lib/y_petri/place.rb +89 -132
- data/lib/y_petri/simulation.rb +191 -168
- data/lib/y_petri/timed_simulation.rb +29 -20
- data/lib/y_petri/transition/arcs.rb +51 -0
- data/lib/y_petri/transition/cocking.rb +32 -0
- data/lib/y_petri/transition/constructor_syntax.rb +378 -0
- data/lib/y_petri/transition.rb +391 -831
- data/lib/y_petri/version.rb +1 -1
- data/lib/y_petri/workspace/parametrized_subclassing.rb +8 -13
- data/lib/y_petri/workspace/simulation_related_methods.rb +13 -11
- data/lib/y_petri.rb +8 -3
- data/test/place_test.rb +83 -0
- data/test/transition_test.rb +325 -0
- data/test/y_petri_test.rb +15 -410
- metadata +12 -2
@@ -0,0 +1,51 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# Connectivity aspect of a transition.
|
4
|
+
#
|
5
|
+
class YPetri::Transition
|
6
|
+
# Names of upstream places.
|
7
|
+
#
|
8
|
+
def domain_pp; domain.map { |p| p.name || p.object_id } end
|
9
|
+
alias :upstream_pp :domain_pp
|
10
|
+
|
11
|
+
# Names of downstream places.
|
12
|
+
#
|
13
|
+
def codomain_pp; codomain.map { |p| p.name || p.object_id } end
|
14
|
+
alias :downstream_pp :codomain_pp
|
15
|
+
|
16
|
+
# Union of action arcs and test arcs.
|
17
|
+
#
|
18
|
+
def arcs; domain | codomain end
|
19
|
+
|
20
|
+
# Returns names of the (places connected to) the transition's arcs.
|
21
|
+
#
|
22
|
+
def aa; arcs.map { |p| p.name || p.object_id } end
|
23
|
+
|
24
|
+
# Marking of the domain places.
|
25
|
+
#
|
26
|
+
def domain_marking; domain.map &:marking end
|
27
|
+
|
28
|
+
# Marking of the codomain places.
|
29
|
+
#
|
30
|
+
def codomain_marking; codomain.map &:marking end
|
31
|
+
|
32
|
+
# Recursive firing of the upstream net portion (honors #cocked?).
|
33
|
+
#
|
34
|
+
def fire_upstream_recursively
|
35
|
+
return false unless cocked?
|
36
|
+
uncock
|
37
|
+
upstream_places.each &:fire_upstream_recursively
|
38
|
+
fire!
|
39
|
+
return true
|
40
|
+
end
|
41
|
+
|
42
|
+
# Recursive firing of the downstream net portion (honors #cocked?).
|
43
|
+
#
|
44
|
+
def fire_downstream_recursively
|
45
|
+
return false unless cocked?
|
46
|
+
uncock
|
47
|
+
fire!
|
48
|
+
downstream_places.each &:fire_downstream_recursively
|
49
|
+
return true
|
50
|
+
end
|
51
|
+
end # class YPetri::Transition
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# Cocking mechanics of a transition. A transition has to be cocked, before
|
4
|
+
# it can succesfuly +#fire+. (+#fire!+ method disregards cocking.)
|
5
|
+
#
|
6
|
+
class YPetri::Transition
|
7
|
+
# Is the transition cocked?
|
8
|
+
#
|
9
|
+
def cocked?
|
10
|
+
@cocked
|
11
|
+
end
|
12
|
+
|
13
|
+
# Negation of +#cocked?+ method.
|
14
|
+
#
|
15
|
+
def uncocked?
|
16
|
+
not cocked?
|
17
|
+
end
|
18
|
+
|
19
|
+
# Cocks teh transition -- allows +#fire+ to succeed.
|
20
|
+
#
|
21
|
+
def cock
|
22
|
+
@cocked = true
|
23
|
+
end
|
24
|
+
alias :cock! :cock
|
25
|
+
|
26
|
+
# Sets the transition state to uncocked.
|
27
|
+
#
|
28
|
+
def uncock
|
29
|
+
@cocked = false
|
30
|
+
end
|
31
|
+
alias :uncock! :uncock
|
32
|
+
end # class YPetri::Transition
|
@@ -0,0 +1,378 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
# Constructor syntax aspect of a transition. Large part of the functionality
|
4
|
+
# of the Transition class is the convenient constructor syntax.
|
5
|
+
#
|
6
|
+
class YPetri::Transition
|
7
|
+
# Transition class represents many different kinds of Petri net transitions.
|
8
|
+
# It makes the constructor syntax a bit more polymorphic. The type of the
|
9
|
+
# transition to construct is mostly inferred from the constructor arguments.
|
10
|
+
#
|
11
|
+
# Mandatorily, the constructor will always need a way to determine the domain
|
12
|
+
# (upstream arcs) and codomain (downstream arcs) of the transition. Also, the
|
13
|
+
# constructor must have a way to determine the transition's action. This is
|
14
|
+
# best explained by examples -- let us have 3 places A, B, C, for whe we will
|
15
|
+
# create different kinds of transitions:
|
16
|
+
#
|
17
|
+
# ==== ts transitions (timeless nonstoichiometric)
|
18
|
+
# Action closure is expected with return arity equal to the codomain size:
|
19
|
+
#
|
20
|
+
# Transition.new upstream_arcs: [A, C], downstream_arcs: [A, B],
|
21
|
+
# action_closure: proc { |m, x|
|
22
|
+
# if x > 0 then [-(m / 2), (m / 2)]
|
23
|
+
# else [1, 0] end
|
24
|
+
# }
|
25
|
+
#
|
26
|
+
# (If C is positive, half of A's marking is moved to B, otherwise A is
|
27
|
+
# incremented by 1.)
|
28
|
+
#
|
29
|
+
# ==== tS transitions (timeless stoichiometric)
|
30
|
+
# Stochiometry has to be supplied, action closure is optional. If supplied,
|
31
|
+
# its return arity should be 1 (to be multiplied by the stochiometry vector).
|
32
|
+
#
|
33
|
+
# If no action closure is given, a _functionless_ transition will be
|
34
|
+
# constructed, with action closure == 1 * stoichiometry vector.
|
35
|
+
#
|
36
|
+
# ==== Tsr transitions (timed rateless nonstoichiometric)
|
37
|
+
# Action closure has to be supplied, whose first argument is Δt, and the
|
38
|
+
# remaining ones correspond to the domain size. Return arity of this closure
|
39
|
+
# should, in turn, correspond to the codomain size.
|
40
|
+
#
|
41
|
+
# ==== TSr transitions (timed rateless stoichiometric)
|
42
|
+
# Action closure has to be supplied, whose first argument is Δt, and the
|
43
|
+
# remaining ones correspond to the domain size. Return arity of this closure
|
44
|
+
# should be 1 (to be multiplied by the stoichiometry vector).
|
45
|
+
#
|
46
|
+
# ==== sR transitions (nonstoichiometric transitions with rate)
|
47
|
+
# Rate closure has to be supplied, whose arity should correspond to the domain
|
48
|
+
# size (Δt argument is not needed). Return arity of this should, in turn,
|
49
|
+
# correspond to the codomain size -- it represents this transition's
|
50
|
+
# contribution to the rate of change of marking of the codomain places.
|
51
|
+
#
|
52
|
+
# ==== SR transitions (stoichiometric transitions with rate)
|
53
|
+
#
|
54
|
+
# Rate closure and stoichiometry has to be supplied. Rate closure arity should
|
55
|
+
# correspond to the domain size. Return arity should be 1 (to be multiplied by
|
56
|
+
# the stoichiometry vector, as in all other stoichiometric transitions).
|
57
|
+
#
|
58
|
+
# Transition.new stoichiometry: { A: -1, B: 1 },
|
59
|
+
# rate: λ { |a| a * 0.5 }
|
60
|
+
#
|
61
|
+
def initialize *args
|
62
|
+
check_in_arguments *args # the big work of checking in args
|
63
|
+
inform_upstream_places # that they have been connected
|
64
|
+
inform_downstream_places # that they have been connected
|
65
|
+
uncock # transitions initialize uncocked
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
# Checking in the arguments supplied to #initialize looks like a big job.
|
71
|
+
# I won't contest to that, but let us not, that it is basically nothing
|
72
|
+
# else then defining the duck type of the input argument collection.
|
73
|
+
# TypeError is therefore raised if invalid collection has been supplied.
|
74
|
+
#
|
75
|
+
def check_in_arguments *aa, **oo, &block
|
76
|
+
oo.may_have :stoichiometry, syn!: [ :stoichio, :s ]
|
77
|
+
oo.may_have :codomain, syn!: [ :codomain_arcs, :codomain_places,
|
78
|
+
:downstream,
|
79
|
+
:downstream_arcs, :downstream_places,
|
80
|
+
:action_arcs ]
|
81
|
+
oo.may_have :domain, syn!: [ :domain_arcs, :domain_places,
|
82
|
+
:upstream, :upstream_arcs, :upstream_places ]
|
83
|
+
oo.may_have :rate, syn!: [ :rate_closure, :propensity,
|
84
|
+
:propensity_closure ]
|
85
|
+
oo.may_have :action, syn!: :action_closure
|
86
|
+
oo.may_have :timed
|
87
|
+
oo.may_have :domain_guard
|
88
|
+
oo.may_have :codomain_guard
|
89
|
+
|
90
|
+
@has_rate = oo.has? :rate # was the rate was given?
|
91
|
+
|
92
|
+
# is the transition stoichiometric (S) or nonstoichiometric (s)?
|
93
|
+
@stoichiometric = oo.has? :stoichiometry
|
94
|
+
|
95
|
+
# downstream description arguments: codomain, stoichiometry (if S)
|
96
|
+
if stoichiometric? then
|
97
|
+
@codomain, @stoichiometry = check_in_downstream_description_for_S( oo )
|
98
|
+
else # s transitions have no stoichiometry
|
99
|
+
@codomain = check_in_downstream_description_for_s( oo )
|
100
|
+
end
|
101
|
+
|
102
|
+
# check in domain first, :missing symbol may appear
|
103
|
+
@domain = check_in_domain( oo )
|
104
|
+
|
105
|
+
# upstream description arguments; also takes care of :missing domain
|
106
|
+
if has_rate? then
|
107
|
+
@domain, @rate_closure, @timed, @functional =
|
108
|
+
check_in_upstream_description_for_R( oo, &block )
|
109
|
+
else
|
110
|
+
@domain, @action_closure, @timed, @functional =
|
111
|
+
check_in_upstream_description_for_r( oo, &block )
|
112
|
+
end
|
113
|
+
|
114
|
+
# optional assignment action:
|
115
|
+
@assignment_action = check_in_assignment_action( oo )
|
116
|
+
|
117
|
+
# optional type guards for domain / codomain:
|
118
|
+
@domain_guard, @codomain_guard = check_in_guards( oo )
|
119
|
+
end # def check_in_arguments
|
120
|
+
|
121
|
+
# Validates that the supplied collection consists only of places of
|
122
|
+
# correct type. Second optional argument customizes the error message.
|
123
|
+
#
|
124
|
+
def sanitize_place_collection place_collection, what_is_collection=nil
|
125
|
+
c = what_is_collection ? what_is_collection.capitalize : "Collection"
|
126
|
+
Array( place_collection ).map do |pl_id|
|
127
|
+
begin
|
128
|
+
place( pl_id )
|
129
|
+
rescue NameError
|
130
|
+
raise TypeError, "#{c} member #{pl_id} does not specify a valid place!"
|
131
|
+
end
|
132
|
+
end.aT what_is_collection, "not contain duplicate places" do |coll|
|
133
|
+
coll == coll.uniq
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Private method, part of #initialize argument checking-in.
|
138
|
+
#
|
139
|
+
def check_in_domain( oo )
|
140
|
+
if oo.has? :domain then
|
141
|
+
sanitize_place_collection( oo[:domain], "supplied domain" )
|
142
|
+
else
|
143
|
+
if stoichiometric? then
|
144
|
+
# take arcs with non-positive stoichiometry coefficients
|
145
|
+
Hash[ [ @codomain, @stoichiometry ].transpose ]
|
146
|
+
.delete_if{ |_place, coeff| coeff > 0 }.keys
|
147
|
+
else
|
148
|
+
:missing
|
149
|
+
# Barring the caller's error, missing domain can mean:
|
150
|
+
# 1. empty domain
|
151
|
+
# 2. domain == codomain
|
152
|
+
# This will be figured later by rate/action closure arity
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Private method, part of the init process when :rate is given. Also takes
|
158
|
+
# care for missing domain (@domain == :missing).
|
159
|
+
#
|
160
|
+
def check_in_upstream_description_for_R( oo, &block )
|
161
|
+
_domain = domain # this method may modify domain
|
162
|
+
fail ArgumentError, "Rate/propensity and action may not be both given!" if
|
163
|
+
oo.has? :action # check against colliding :action named argument
|
164
|
+
fail ArgumentError, "If block is given, rate must not be given!" if block
|
165
|
+
# Let's figure the rate closure now. (Block is never used.)
|
166
|
+
rate_λ = case ra = oo[:rate]
|
167
|
+
when Proc then # We received the closure directly,
|
168
|
+
ra.tap do |λ| # but we've to be concerned about missing domain.
|
169
|
+
if domain == :missing then # we've to figure user's intent
|
170
|
+
_domain = if λ.arity == 0 then [] # user meant empty domain
|
171
|
+
else codomain end # user meant domain == codomain
|
172
|
+
else # domain not missing
|
173
|
+
fail TypeError, "Rate closure arity (#{λ.arity}) > " +
|
174
|
+
"domain (#{domain.size})!" if λ.arity.abs > domain.size
|
175
|
+
end
|
176
|
+
end
|
177
|
+
else # We received something else, must guess user's intent.
|
178
|
+
if stoichiometric? then # user's intent was mass action
|
179
|
+
fail TypeError, "When a number is supplied as rate, " +
|
180
|
+
"domain must not be given!" if oo.has? :domain
|
181
|
+
construct_standard_mass_action( ra )
|
182
|
+
else # user's intent was constant closure
|
183
|
+
fail TypeError, "When rate is a number and stoichiometry " +
|
184
|
+
"is not given, codomain size must be 1!" unless
|
185
|
+
codomain.size == 1
|
186
|
+
# Missing domain is OK here,
|
187
|
+
_domain = [] if domain == :missing
|
188
|
+
# but if it was supplied explicitly, it must be empty.
|
189
|
+
fail TypeError, "Rate is a number, but non-empty domain " +
|
190
|
+
"was supplied!" unless domain.empty? if oo.has?( :domain )
|
191
|
+
-> { ra }
|
192
|
+
end
|
193
|
+
end
|
194
|
+
# R transitions are implicitly timed
|
195
|
+
_timed = true
|
196
|
+
# check against colliding :timed argument
|
197
|
+
oo[:timed].tE :timed, "not be false if rate given" if oo.has? :timed
|
198
|
+
# R transitions are implicitly functional
|
199
|
+
_functional = true
|
200
|
+
return _domain, rate_λ, _timed, _functional
|
201
|
+
end
|
202
|
+
|
203
|
+
# Private method, part of the init process when :rate is not given. Also
|
204
|
+
# takes care for missing domain (@domain == :missing).
|
205
|
+
#
|
206
|
+
def check_in_upstream_description_for_r( oo, &block )
|
207
|
+
_domain = domain # this method may modify domain
|
208
|
+
_functional = true
|
209
|
+
# Was action closure was given explicitly?
|
210
|
+
if oo.has? :action then
|
211
|
+
fail ArgumentError, "If block is given, rate must not be given!" if block
|
212
|
+
action_λ = oo[:action].aT_is_a Proc, "supplied action named argument"
|
213
|
+
if oo.has? :timed then
|
214
|
+
_timed = oo[:timed]
|
215
|
+
# Time to worry about the domain_missing
|
216
|
+
if domain == :missing then # figure user's intent from closure arity
|
217
|
+
_domain = if action_λ.arity == ( _timed ? 1 : 0 ) then
|
218
|
+
[] # user meant empty domain
|
219
|
+
else
|
220
|
+
codomain # user meant domain same as codomain
|
221
|
+
end
|
222
|
+
else # domain not missing
|
223
|
+
fail TypeError, "Rate closure arity (#{rate_arg.arity}) > domain " +
|
224
|
+
"size (#{domain.size})!" if action_λ.arity.abs > domain.size
|
225
|
+
end
|
226
|
+
else # :timed argument not supplied
|
227
|
+
if domain == :missing then
|
228
|
+
# If no domain was supplied, there is no way to reasonably figure
|
229
|
+
# out the user's intent, except when arity is 0:
|
230
|
+
_domain = case action_λ.arity
|
231
|
+
when 0 then
|
232
|
+
_timed = false
|
233
|
+
[] # empty domain is implied
|
234
|
+
else # no deduction of user intent possible
|
235
|
+
fail ArgumentError, "Too much ambiguity: Rateless " +
|
236
|
+
"transition with neither domain nor timedness given."
|
237
|
+
end
|
238
|
+
else # domain not missing
|
239
|
+
# Even if the user did not bother to inform us explicitly about
|
240
|
+
# timedness, we can use closure arity as a clue. If it equals the
|
241
|
+
# domain size, leaving no room for Δtime argument, the user intent
|
242
|
+
# was to create timeless transition. If it equals domain size + 1,
|
243
|
+
# theu user intended to create a timed transition.
|
244
|
+
_timed = case action_λ.arity
|
245
|
+
when domain.size then false
|
246
|
+
when domain.size + 1 then true
|
247
|
+
else # no deduction of user intent possible
|
248
|
+
fail ArgumentError, "Timedness was not specified, and " +
|
249
|
+
"action closure arity (#{action_λ.arity}) does not " +
|
250
|
+
"give a clear hint on it!"
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
else # rateless cases with no action closure specified
|
255
|
+
# Consume block, if given:
|
256
|
+
check_in_upstream_for_r oo.update( action: block ) if block
|
257
|
+
# If there is really really no closure, an assumption must be made taken
|
258
|
+
# as for the transition's action, in particular, -> { 1 } closure:
|
259
|
+
action_λ = -> { 1 }
|
260
|
+
# The transition is then required to be stoichiometric and timeless.
|
261
|
+
# Domain will be required empty.
|
262
|
+
fail ArgumentError, "Stoichiometry is compulsory, if no rate/action " +
|
263
|
+
"was supplied." unless stoichiometric?
|
264
|
+
# With this, we can drop worries about missing domain.
|
265
|
+
fail ArgumentError, "When no rate/propensity or action is supplied, " +
|
266
|
+
"the transition cannot be timed." if oo[:timed] if oo.has? :timed
|
267
|
+
_timed = false
|
268
|
+
_domain = []
|
269
|
+
_functional = false # the transition is considered functionless
|
270
|
+
end
|
271
|
+
return _domain, action_λ, _timed, _functional
|
272
|
+
end
|
273
|
+
|
274
|
+
# Default rate closure for SR transitions whose rate is hinted as a number.
|
275
|
+
#
|
276
|
+
def construct_standard_mass_action( num )
|
277
|
+
# assume standard mass-action law
|
278
|
+
nonpositive_coeffs = stoichiometry.select { |coeff| coeff <= 0 }
|
279
|
+
# the closure takes markings of the domain as its arguments
|
280
|
+
-> *markings do
|
281
|
+
nonpositive_coeffs.size.times.reduce num do |acc, i|
|
282
|
+
marking, coeff = markings[ i ], nonpositive_coeffs[ i ]
|
283
|
+
# Stoichiometry coefficients equal to zero are taken to indicate
|
284
|
+
# plain factors, assuming that if these places were not involved
|
285
|
+
# in the transition at all, the user would not be mentioning them.
|
286
|
+
case coeff
|
287
|
+
when 0, -1 then marking * acc
|
288
|
+
else marking ** -coeff end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
# Private method, checking in downstream specification from the argument
|
294
|
+
# field for stoichiometric transition.
|
295
|
+
#
|
296
|
+
def check_in_downstream_description_for_S( oo )
|
297
|
+
codomain, stoichio =
|
298
|
+
case oo[:stoichiometry]
|
299
|
+
when Hash then
|
300
|
+
# contains pairs { codomain place => stoichiometry coefficient }
|
301
|
+
fail ArgumentError, "With hash-type stoichiometry, :codomain " +
|
302
|
+
"argument must not be given!" if oo.has? :codomain
|
303
|
+
oo[:stoichiometry].each_with_object [[], []] do |(cd_pl, coeff), memo|
|
304
|
+
memo[0] << cd_pl
|
305
|
+
memo[1] << coeff
|
306
|
+
end
|
307
|
+
else
|
308
|
+
# array of stoichiometry coefficients
|
309
|
+
fail ArgumentError, "With array-type stoichiometry, :codomain " +
|
310
|
+
"argument must be given!" unless oo.has? :codomain
|
311
|
+
[ oo[:codomain], Array( oo[:stoichiometry] ) ]
|
312
|
+
end
|
313
|
+
# enforce that stoichiometry is a collection of numbers
|
314
|
+
return sanitize_place_collection( codomain, "supplied codomain" ),
|
315
|
+
stoichio.aT_all_numeric( "supplied stoichiometry" )
|
316
|
+
end
|
317
|
+
|
318
|
+
# Private method, checking in downstream specification from the argument
|
319
|
+
# field for nonstoichiometric transition.
|
320
|
+
#
|
321
|
+
def check_in_downstream_description_for_s( oo )
|
322
|
+
# codomain must be explicitly given - no way around it:
|
323
|
+
fail ArgumentError, "For non-stoichiometric transitions, :codomain " +
|
324
|
+
"argument is compulsory." unless oo.has? :codomain
|
325
|
+
return sanitize_place_collection( oo[:codomain], "supplied codomain" )
|
326
|
+
end
|
327
|
+
|
328
|
+
# Private method, part of #initialize argument checking-in.
|
329
|
+
#
|
330
|
+
def check_in_assignment_action( oo )
|
331
|
+
if oo.has? :assignment_action, syn!: [ :assignment, :assign, :A ] then
|
332
|
+
if timed? then
|
333
|
+
false.tap do
|
334
|
+
msg = "Timed transitions may not have assignment action!"
|
335
|
+
raise TypeError, msg if oo[:assignment_action]
|
336
|
+
end
|
337
|
+
else oo[:assignment_action] end # only timeless transitions are eligible
|
338
|
+
else false end # the default value
|
339
|
+
end
|
340
|
+
|
341
|
+
# Private method, part of #initialize argument checking-in
|
342
|
+
#
|
343
|
+
def check_in_guards( oo )
|
344
|
+
if oo.has? :domain_guard then
|
345
|
+
oo[:domain_guard].aT_is_a Proc, "supplied domain guard"
|
346
|
+
else
|
347
|
+
place_guards = domain_places.map &:guard
|
348
|
+
-> dm do # constructing the default domain guard
|
349
|
+
fails = [domain, dm, place_guards].transpose.map { |pl, m, guard|
|
350
|
+
[ pl, m, begin; guard.( m ); true; rescue YPetri::GuardError; false end ]
|
351
|
+
}.reduce [] do |memo, triple| memo << triple unless triple[2] end
|
352
|
+
# TODO: Watch "Exceptional Ruby" video by Avdi Grimm.
|
353
|
+
unless fails.size == 0
|
354
|
+
fail YPetri::GuardError, "Domain guard of #{self} rejects marking " +
|
355
|
+
if fails.size == 1 then
|
356
|
+
p, m, _ = fails[0]
|
357
|
+
"#{m} of place #{p.name || p.object_id}!"
|
358
|
+
else
|
359
|
+
"of the following places: %s!" %
|
360
|
+
Hash[ fails.map { |pl, m, _| [pl.name || pl.object_id, m] } ]
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
# Informs upstream places that they have been connected to this transition.
|
368
|
+
#
|
369
|
+
def inform_upstream_places
|
370
|
+
upstream_places.each { |p| p.send :register_downstream_transition, self }
|
371
|
+
end
|
372
|
+
|
373
|
+
# Informs downstream places that they are connected to this transition.
|
374
|
+
#
|
375
|
+
def inform_downstream_places
|
376
|
+
downstream_places.each { |p| p.send :register_upstream_transition, self }
|
377
|
+
end
|
378
|
+
end # class YPetri::Transition
|