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,1313 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
# Emphasizing separation of concerns, the model is defined as agnostic of
|
4
|
+
# simulation settings. Only for the purpose of simulation, model is combined
|
5
|
+
# together with specific simulation settings. Simulation settings consist of
|
6
|
+
# global settings (eg. time step, sampling rate...) and object specific
|
7
|
+
# settings (eg. clamps, constraints...). Again, clamps and constraints *do
|
8
|
+
# not* belong to the model. Simulation methods are also concern of this
|
9
|
+
# class, not the model class. Thus, simulation is not done by calling
|
10
|
+
# instance methods of the model. Instead, this class makes a 'mental image'
|
11
|
+
# of the model and only that is used for actual simulation.
|
12
|
+
#
|
13
|
+
class YPetri::Simulation
|
14
|
+
SAMPLING_DECIMAL_PLACES = 5
|
15
|
+
SIMULATION_METHODS =
|
16
|
+
[
|
17
|
+
[:pseudo_Euler] # pseudo-timed simulation (like in Cell Illustrator)
|
18
|
+
]
|
19
|
+
DEFAULT_SIMULATION_METHOD = :pseudo_Euler
|
20
|
+
|
21
|
+
# Default simulation method (accesses the constant DEFAULT_SIMULATION_METHOD
|
22
|
+
# in the receiver's class).
|
23
|
+
#
|
24
|
+
def default_simulation_method
|
25
|
+
self.class.const_get :DEFAULT_SIMULATION_METHOD
|
26
|
+
end
|
27
|
+
|
28
|
+
# Exposing @recording
|
29
|
+
#
|
30
|
+
attr_reader :recording
|
31
|
+
alias :r :recording
|
32
|
+
|
33
|
+
# Simulation settings.
|
34
|
+
#
|
35
|
+
def settings; {} end
|
36
|
+
alias :simulation_settings :settings
|
37
|
+
|
38
|
+
def recording_csv_string
|
39
|
+
CSV.generate do |csv|
|
40
|
+
@recording.keys.zip( @recording.values ).map{ |a, b| [ a ] + b.to_a }
|
41
|
+
.each{ |line| csv << line }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Currently, a simulation instance is largely immutable. It means that
|
46
|
+
# the net, initial marking, clamps and simulation settings have to be
|
47
|
+
# supplied upon initialization, whereupon the simulation forms their
|
48
|
+
# "mental image", which does not change anymore, regardless of what happens
|
49
|
+
# to the original net and other objects. Required constructor parameters
|
50
|
+
# are :net, :place_clamps (alias :marking_clamps) and :initial_marking
|
51
|
+
# (alias :initial_marking_vector). (Simulation subclasses may require other
|
52
|
+
# arguments in addition to the ones just named.)
|
53
|
+
#
|
54
|
+
def initialize args={}
|
55
|
+
puts "starting to set up Simulation" if YPetri::DEBUG
|
56
|
+
|
57
|
+
args.may_have :method, syn!: :simulation_method
|
58
|
+
args.must_have :net do |o| o.class_complies? ::YPetri::Net end
|
59
|
+
args.may_have :place_clamps, syn!: :marking_clamps
|
60
|
+
args.may_have :initial_marking, syn!: :initial_marking_vector
|
61
|
+
|
62
|
+
# ==== Simulation method
|
63
|
+
#
|
64
|
+
@method = args[:method] || default_simulation_method()
|
65
|
+
|
66
|
+
# ==== Net
|
67
|
+
#
|
68
|
+
@net = args[:net].dup # @immutable within the instance
|
69
|
+
@places = @net.places.dup
|
70
|
+
@transitions = @net.transitions.dup
|
71
|
+
|
72
|
+
self.singleton_class.class_exec {
|
73
|
+
define_method :Place do net.send :Place end
|
74
|
+
define_method :Transition do net.send :Transition end
|
75
|
+
define_method :Net do net.send :Net end
|
76
|
+
private :Place, :Transition, :Net
|
77
|
+
}
|
78
|
+
|
79
|
+
puts "setup of :net mental image complete" if YPetri::DEBUG
|
80
|
+
|
81
|
+
# ==== Simulation parameters
|
82
|
+
#
|
83
|
+
# A simulation distinguishes between free and clamped places. For free
|
84
|
+
# places, initial value has to be specified. For clamped places, clamps
|
85
|
+
# have to be specified. Both initial values and clamps are expected as
|
86
|
+
# hash-type named parameters:
|
87
|
+
@place_clamps = ( args[:place_clamps] || {} ).with_keys { |k| place k }
|
88
|
+
@initial_marking = ( args[:initial_marking] || {} ).with_keys { |k| place k }
|
89
|
+
|
90
|
+
# Enforce that keys in the hashes must be unique:
|
91
|
+
@place_clamps.keys.aT_equal @place_clamps.keys.uniq
|
92
|
+
@initial_marking.keys.aT_equal @initial_marking.keys.uniq
|
93
|
+
|
94
|
+
puts "setup of clamps and initial marking done" if YPetri::DEBUG
|
95
|
+
|
96
|
+
# === Consistency check
|
97
|
+
#
|
98
|
+
# # Clamped places must not have explicit initial marking specified:
|
99
|
+
# @place_clamps.keys.each { |place|
|
100
|
+
# place.aT_not "clamped place #{place}",
|
101
|
+
# "have explicitly specified initial marking" do |place|
|
102
|
+
# @initial_marking.keys.include? place
|
103
|
+
# end
|
104
|
+
# }
|
105
|
+
|
106
|
+
# Each place must be treated: either clamped, or have initial marking
|
107
|
+
places.each { |p|
|
108
|
+
p.aT "place #{p}", "have either clamp or initial marking" do |p|
|
109
|
+
@place_clamps.keys.include?( p ) || @initial_marking.keys.include?( p )
|
110
|
+
end
|
111
|
+
}
|
112
|
+
|
113
|
+
puts "consistency check for clamps and initial marking passed" if YPetri::DEBUG
|
114
|
+
|
115
|
+
# === Correspondence matrices.
|
116
|
+
|
117
|
+
# Multiplying this matrix by marking vector for free places (ᴍ) gives
|
118
|
+
# ᴍ mapped for all places.
|
119
|
+
@F2A = Matrix.correspondence_matrix( free_places, places )
|
120
|
+
|
121
|
+
# Multiplying this matrix by marking vector for clamped places maps that
|
122
|
+
# vector to all places.
|
123
|
+
@C2A = Matrix.correspondence_matrix( clamped_places, places )
|
124
|
+
|
125
|
+
puts "correspondence matrices set up" if YPetri::DEBUG
|
126
|
+
|
127
|
+
# --- Stoichiometry matrices ----
|
128
|
+
@S_for_tS = S_for tS_transitions()
|
129
|
+
@S_for_SR = S_for SR_transitions()
|
130
|
+
@S_for_TSr = S_for TSr_transitions()
|
131
|
+
|
132
|
+
puts "stoichiometry matrices set up" if YPetri::DEBUG
|
133
|
+
|
134
|
+
# ----- Create other assets -----
|
135
|
+
@Δ_closures_for_ts = create_Δ_closures_for_ts
|
136
|
+
@Δ_closures_for_Tsr = create_Δ_closures_for_Tsr
|
137
|
+
@action_closures_for_tS = create_action_closures_for_tS
|
138
|
+
@action_closures_for_TSr = create_action_closures_for_TSr
|
139
|
+
@rate_closures_for_sR = create_rate_closures_for_sR
|
140
|
+
@rate_closures_for_SR = create_rate_closures_for_SR
|
141
|
+
|
142
|
+
@assignment_closures_for_A = create_assignment_closures_for_A
|
143
|
+
|
144
|
+
@zero_ᴍ = Matrix.zero( free_places.size, 1 )
|
145
|
+
|
146
|
+
puts "other assets set up, about to reset" if YPetri::DEBUG
|
147
|
+
|
148
|
+
# ----------- Reset -------------
|
149
|
+
reset!
|
150
|
+
|
151
|
+
puts "reset complete" if YPetri::DEBUG
|
152
|
+
end
|
153
|
+
|
154
|
+
# Returns a new instance of the system at a different state, while leaving
|
155
|
+
# all other simulation settings unchanged. This desired state can be
|
156
|
+
# specified either as marking vector (:ᴍ), marking array of free places (:m),
|
157
|
+
# marking array of all places (:marking), or marking hash (:pm). In case of
|
158
|
+
# marking hash, it does not have to be given for each place, missing places
|
159
|
+
# will be left unchanged.
|
160
|
+
#
|
161
|
+
def at *args
|
162
|
+
oo = args.extract_options!
|
163
|
+
oo.may_have :m # marking of free places
|
164
|
+
oo.may_have :marking # marking of all places
|
165
|
+
oo.may_have :ᴍ, syn!: :m_vector # marking vector of free places
|
166
|
+
oo.may_have :marking_vector # marking vector of all places
|
167
|
+
oo.may_have :pm, syn!: [ :p_m, :pmarking, :p_marking, # marking hash
|
168
|
+
:place_m, :place_marking ]
|
169
|
+
if oo.has? :marking_vector then
|
170
|
+
duplicate.send :set_marking_vector, oo.delete( :marking_vector )
|
171
|
+
elsif oo.has? :marking then
|
172
|
+
duplicate.send :set_marking, oo.delete( :marking )
|
173
|
+
elsif oo.has? :m then
|
174
|
+
duplicate.send :set_m, oo.delete( :m )
|
175
|
+
elsif oo.has? :ᴍ then
|
176
|
+
duplicate.send :set_ᴍ, oo.delete( :ᴍ )
|
177
|
+
elsif oo.has? :pm then
|
178
|
+
duplicate.send :set_pm, oo.delete( :pm )
|
179
|
+
else
|
180
|
+
duplicate.send :set_pm, oo
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Exposing @net.
|
185
|
+
#
|
186
|
+
attr_reader :net
|
187
|
+
|
188
|
+
# Without arguments or block, it returns simply a list of places. Otherwise,
|
189
|
+
# it returns a has whose keys are the places, and whose values are governed
|
190
|
+
# by the supplied parameters (either another collection, or message to #send
|
191
|
+
# to self to obtain a second collection).
|
192
|
+
#
|
193
|
+
def places *aa, &b
|
194
|
+
return @places.dup if aa.empty? && b.nil?
|
195
|
+
zip_to_hash places, *aa, &b
|
196
|
+
end
|
197
|
+
|
198
|
+
# Without arguments or block, it returns simply a list of transitions.
|
199
|
+
# Otherwise, it returns a has whose keys are the places, and whose values are
|
200
|
+
# governed by the supplied parameters (either another collection, or message
|
201
|
+
# to #send to self to obtain a second collection).
|
202
|
+
#
|
203
|
+
def transitions *aa, &b
|
204
|
+
return @transitions.dup if aa.empty? && b.nil?
|
205
|
+
zip_to_hash transitions, *aa, &b
|
206
|
+
end
|
207
|
+
|
208
|
+
# Without arguments or block, it returns simply a list of place names.
|
209
|
+
# Otherwise, it returns a hash whose keys are place names, and whose values
|
210
|
+
# are determined by the supplied argument(s) and/or block (either another
|
211
|
+
# collection, or a message to #send to self to obtain such collection). Unary
|
212
|
+
# block can be supplied to modify these values.
|
213
|
+
#
|
214
|
+
def pp *aa, &b
|
215
|
+
return places.map &:name if aa.empty? && b.nil?
|
216
|
+
zip_to_hash( places.map { |p| p.name || p }, *aa, &b )
|
217
|
+
end
|
218
|
+
|
219
|
+
# Without arguments or block, it returns simply a list of transition names.
|
220
|
+
# Otherwise, it returns a hash whose keys are transition names, and whose
|
221
|
+
# values are determined by the supplied argument(s) and/or block (either
|
222
|
+
# another collection, or a message to #send to self to obtain such collection).
|
223
|
+
# Unary block can be supplied to modify these values.
|
224
|
+
#
|
225
|
+
def tt *aa, &b
|
226
|
+
return transitions.map &:name if aa.empty? && b.nil?
|
227
|
+
zip_to_hash( transitions.map { |t| t.name || t }, *aa, &b )
|
228
|
+
end
|
229
|
+
|
230
|
+
# Without arguments or block, it returns simply a list of free places.
|
231
|
+
# Otherwise, it returns a hash, whose keys are the free places, and whose
|
232
|
+
# values are governed by the supplied parameters (either another collection,
|
233
|
+
# or message to #send to self to obtain a second collection).
|
234
|
+
#
|
235
|
+
def free_places *aa, &b
|
236
|
+
return zip_to_hash free_places, *aa, &b unless aa.empty? && b.nil?
|
237
|
+
kk = @initial_marking.keys
|
238
|
+
places.select { |p| kk.include? p }
|
239
|
+
end
|
240
|
+
|
241
|
+
# Behaves like #free_places, except that it uses place names instead of
|
242
|
+
# instances whenever possible.
|
243
|
+
#
|
244
|
+
def free_pp *aa, &b
|
245
|
+
return free_places.map { |p| p.name || p } if aa.empty? && b.nil?
|
246
|
+
zip_to_hash free_pp, *aa, &b
|
247
|
+
end
|
248
|
+
|
249
|
+
# Initial marking definitions for free places (array).
|
250
|
+
#
|
251
|
+
def im
|
252
|
+
free_places.map { |p| @initial_marking[p] }
|
253
|
+
end
|
254
|
+
|
255
|
+
# Marking array of all places as it appears at the beginning of a simulation.
|
256
|
+
#
|
257
|
+
def initial_marking
|
258
|
+
raise # FIXME: "Initial marking" for all places (ie. incl. clamped ones).
|
259
|
+
end
|
260
|
+
|
261
|
+
# Initial marking of free places as a column vector.
|
262
|
+
#
|
263
|
+
def im_vector
|
264
|
+
Matrix.column_vector im
|
265
|
+
end
|
266
|
+
alias iᴍ im_vector
|
267
|
+
|
268
|
+
# Marking of all places at the beginning of a simulation, as a column vector.
|
269
|
+
#
|
270
|
+
def initial_marking_vector
|
271
|
+
Matrix.column_vector initial_marking
|
272
|
+
end
|
273
|
+
|
274
|
+
# Without arguments or block, it returns simply a list of clamped places.
|
275
|
+
# Otherwise, it returns a hash, whose keys are the places, and whose values
|
276
|
+
# are governed by the supplied parameters (either another collection, or
|
277
|
+
# message to #send to self to obtain a second collection).
|
278
|
+
#
|
279
|
+
def clamped_places *aa, &b
|
280
|
+
return zip_to_hash clamped_places, *aa, &b unless aa.empty? && b.nil?
|
281
|
+
kk = @place_clamps.keys
|
282
|
+
places.select { |p| kk.include? p }
|
283
|
+
end
|
284
|
+
|
285
|
+
# Behaves like #clamped_places, except that it uses place names instead of
|
286
|
+
# instances whenever possible.
|
287
|
+
#
|
288
|
+
def clamped_pp *aa, &b
|
289
|
+
return clamped_places.map { |p| p.name || p } if aa.empty? && b.nil?
|
290
|
+
zip_to_hash clamped_pp, *aa, &b
|
291
|
+
end
|
292
|
+
|
293
|
+
# Place clamp definitions for clamped places (array)
|
294
|
+
#
|
295
|
+
def place_clamps
|
296
|
+
clamped_places.map { |p| @place_clamps[p] }
|
297
|
+
end
|
298
|
+
|
299
|
+
# Marking array of free places.
|
300
|
+
#
|
301
|
+
def m
|
302
|
+
m_vector.column_to_a
|
303
|
+
end
|
304
|
+
|
305
|
+
# Marking hash of free places { name: marking }.
|
306
|
+
#
|
307
|
+
def pm
|
308
|
+
free_pp :m
|
309
|
+
end
|
310
|
+
alias p_m pm
|
311
|
+
|
312
|
+
# Marking hash of free places { place: marking }.
|
313
|
+
#
|
314
|
+
def place_m
|
315
|
+
free_places :m
|
316
|
+
end
|
317
|
+
|
318
|
+
# Marking array of all places.
|
319
|
+
#
|
320
|
+
def marking
|
321
|
+
marking_vector ? marking_vector.column_to_a : nil
|
322
|
+
end
|
323
|
+
|
324
|
+
# Marking hash of all places { name: marking }.
|
325
|
+
#
|
326
|
+
def pmarking
|
327
|
+
pp :marking
|
328
|
+
end
|
329
|
+
alias p_marking pmarking
|
330
|
+
|
331
|
+
# Marking hash of all places { place: marking }.
|
332
|
+
#
|
333
|
+
def place_marking
|
334
|
+
places :marking
|
335
|
+
end
|
336
|
+
|
337
|
+
# Marking of a specified place(s)
|
338
|
+
#
|
339
|
+
def marking_of place_or_collection_of_places
|
340
|
+
if place_or_collection_of_places.respond_to? :each then
|
341
|
+
place_or_collection_of_places.map { |pl| place_marking[ place( pl ) ] }
|
342
|
+
else
|
343
|
+
place_marking[ place( place_or_collection_of_places ) ]
|
344
|
+
end
|
345
|
+
end
|
346
|
+
alias m_of marking_of
|
347
|
+
|
348
|
+
# Marking of free places as a column vector.
|
349
|
+
#
|
350
|
+
def m_vector
|
351
|
+
F2A().t * @marking_vector
|
352
|
+
end
|
353
|
+
alias ᴍ m_vector
|
354
|
+
|
355
|
+
# Marking of clamped places as a column vector.
|
356
|
+
#
|
357
|
+
def marking_vector_of_clamped_places
|
358
|
+
C2A().t * @marking_vector
|
359
|
+
end
|
360
|
+
alias ᴍ_clamped marking_vector_of_clamped_places
|
361
|
+
|
362
|
+
# Marking of clamped places as an array.
|
363
|
+
#
|
364
|
+
def marking_of_clamped_places
|
365
|
+
ᴍ_clamped.column( 0 ).to_a
|
366
|
+
end
|
367
|
+
alias m_clamped marking_of_clamped_places
|
368
|
+
|
369
|
+
# Marking of all places as a column vector.
|
370
|
+
#
|
371
|
+
attr_reader :marking_vector
|
372
|
+
|
373
|
+
# Creation of stoichiometry matrix for an arbitrary array of stoichio.
|
374
|
+
# transitions, that maps (has the number of rows equal to) the free places.
|
375
|
+
#
|
376
|
+
def S_for( array_of_S_transitions )
|
377
|
+
array_of_S_transitions.map { |t| sparse_σ t }
|
378
|
+
.reduce( Matrix.empty( free_places.size, 0 ), :join_right )
|
379
|
+
end
|
380
|
+
|
381
|
+
# Creation of stoichiometry matrix for an arbitrary array of stoichio.
|
382
|
+
# transitions, that maps (has the number of rows equal to) all the places.
|
383
|
+
#
|
384
|
+
def stoichiometry_matrix_for( array_of_S_transitions )
|
385
|
+
array_of_S_transitions.map { |t| sparse_stoichiometry_vector t }
|
386
|
+
.reduce( Matrix.empty( places.size, 0 ), :join_right )
|
387
|
+
end
|
388
|
+
|
389
|
+
# 3. Stoichiometry matrix for timeless stoichiometric transitions.
|
390
|
+
#
|
391
|
+
attr_reader :S_for_tS
|
392
|
+
|
393
|
+
# 4. Stoichiometry matrix for timed rateless stoichiometric transitions.
|
394
|
+
#
|
395
|
+
attr_reader :S_for_TSr
|
396
|
+
|
397
|
+
# 6. Stoichiometry matrix for stoichiometric transitions with rate.
|
398
|
+
#
|
399
|
+
attr_reader :S_for_SR
|
400
|
+
|
401
|
+
# Stoichiometry matrix, with the distinction, that the caller asserts,
|
402
|
+
# that all transitions in this simulation are stoichiometric transitions
|
403
|
+
# with rate (or error).
|
404
|
+
#
|
405
|
+
def S
|
406
|
+
return S_for_SR() if s_transitions.empty? && r_transitions.empty?
|
407
|
+
raise "The simulation contains also non-stoichiometric transitions! " +
|
408
|
+
"Consider using #S_for_SR."
|
409
|
+
end
|
410
|
+
|
411
|
+
# ==== 1. Exposing ts transitions
|
412
|
+
|
413
|
+
# Without arguments or block, it returns simply a list of timeless
|
414
|
+
# nonstoichiometric transitions. Otherwise, it returns a hash, whose keys
|
415
|
+
# are the ts transitions, and values are governed by the supplied parameters
|
416
|
+
# (either another collection, or a message to #send to self to obtain the
|
417
|
+
# collection of values).
|
418
|
+
#
|
419
|
+
def ts_transitions *aa, &b
|
420
|
+
return zip_to_hash ts_transitions, *aa, &b unless aa.empty? && b.nil?
|
421
|
+
sift_from_net :ts_transitions
|
422
|
+
end
|
423
|
+
|
424
|
+
# Like #ts_transitions, except that transition names are used instead of
|
425
|
+
# instances, whenever possible.
|
426
|
+
#
|
427
|
+
def ts_tt *aa, &b
|
428
|
+
return zip_to_hash ts_tt, *aa, &b unless aa.empty? && b.nil?
|
429
|
+
ts_transitions.map { |t| t.name || t }
|
430
|
+
end
|
431
|
+
|
432
|
+
# ==== 2. Exposing tS transitions
|
433
|
+
|
434
|
+
# Without arguments or block, it returns simply a list of timeless
|
435
|
+
# stoichiometric transitions. Otherwise, it returns a hash, whose keys are
|
436
|
+
# the tS transitions, and values are governed by the supplied parameters
|
437
|
+
# (either another collection, or a message to #send to self to obtain the
|
438
|
+
# collection of values).
|
439
|
+
#
|
440
|
+
def tS_transitions *aa, &b
|
441
|
+
return zip_to_hash tS_transitions, *aa, &b unless aa.empty? && b.nil?
|
442
|
+
sift_from_net :tS_transitions
|
443
|
+
end
|
444
|
+
|
445
|
+
# Like #tS_transitions, except that transition names are used instead of
|
446
|
+
# instances, whenever possible.
|
447
|
+
#
|
448
|
+
def tS_tt *aa, &b
|
449
|
+
return zip_to_hash tS_tt, *aa, &b unless aa.empty? && b.nil?
|
450
|
+
tS_transitions.map { |t| t.name || t }
|
451
|
+
end
|
452
|
+
|
453
|
+
# ==== 3. Exposing Tsr transitions
|
454
|
+
|
455
|
+
# Without arguments or block, it returns simply a list of timed rateless
|
456
|
+
# nonstoichiometric transitions. Otherwise, it returns a hash, whose keys
|
457
|
+
# are the Tsr transitions, and whose values are governed by the supplied
|
458
|
+
# arguments (either an explicit collection of values, or a message to #send
|
459
|
+
# to self to obtain such collection).
|
460
|
+
#
|
461
|
+
def Tsr_transitions *aa, &b
|
462
|
+
return zip_to_hash Tsr_transitions(), *aa, &b unless aa.empty? && b.nil?
|
463
|
+
sift_from_net :Tsr_transitions
|
464
|
+
end
|
465
|
+
|
466
|
+
# Like #Tsr_transitions, except that transition names are used instead of
|
467
|
+
# instances, whenever possible.
|
468
|
+
#
|
469
|
+
def Tsr_tt *aa, &b
|
470
|
+
return zip_to_hash Tsr_tt(), *aa, &b unless aa.empty? && b.nil?
|
471
|
+
Tsr_transitions().map { |t| t.name || t }
|
472
|
+
end
|
473
|
+
|
474
|
+
# ==== 4. Exposing TSr transitions
|
475
|
+
|
476
|
+
# Without arguments or block, it returns simply a list of timed rateless
|
477
|
+
# stoichiometric transitions. Otherwise, it returns a hash, whose keys are
|
478
|
+
# are the TSr transitions, and whose values are governed by the supplied
|
479
|
+
# arguments (either an explicit collection of values, or a message to #send
|
480
|
+
# to self to obtain such collection).
|
481
|
+
#
|
482
|
+
def TSr_transitions *aa, &b
|
483
|
+
return zip_to_hash TSr_transitions(), *aa, &b unless aa.empty? && b.nil?
|
484
|
+
sift_from_net :TSr_transitions
|
485
|
+
end
|
486
|
+
|
487
|
+
# Like #TSr_transitions, except that transition names are used instead of
|
488
|
+
# instances, whenever possible.
|
489
|
+
#
|
490
|
+
def TSr_tt *aa, &b
|
491
|
+
return zip_to_hash TSr_tt(), *aa, &b unless aa.empty? && b.nil?
|
492
|
+
TSr_transitions().map { |t| t.name || t }
|
493
|
+
end
|
494
|
+
|
495
|
+
# ==== 5. Exposing sR transitions
|
496
|
+
|
497
|
+
# Without arguments or block, it returns simply a list of nonstoichiometric
|
498
|
+
# transitions with rate. Otherwise, it returns a hash, whose keys are
|
499
|
+
# are the sR transitions, and whose values are governed by the supplied
|
500
|
+
# arguments (either an explicit collection of values, or a message to #send
|
501
|
+
# to self to obtain such collection).
|
502
|
+
#
|
503
|
+
def sR_transitions *aa, &b
|
504
|
+
return zip_to_hash sR_transitions(), *aa, &b unless aa.empty? && b.nil?
|
505
|
+
sift_from_net :sR_transitions
|
506
|
+
end
|
507
|
+
|
508
|
+
# Like #sR_transitions, except that transition names are used instead of
|
509
|
+
# instances, whenever possible.
|
510
|
+
#
|
511
|
+
def sR_tt *aa, &b
|
512
|
+
return zip_to_hash sR_tt(), *aa, &b unless aa.empty? && b.nil?
|
513
|
+
sR_transitions.map { |t| t.name || t }
|
514
|
+
end
|
515
|
+
|
516
|
+
# ==== 6. Exposing SR transitions
|
517
|
+
|
518
|
+
# Without arguments or block, it returns simply a list of stoichiometric
|
519
|
+
# transitions with rate. Otherwise, it returns a hash, whose keys are
|
520
|
+
# are the SR transitions, and whose values are governed by the supplied
|
521
|
+
# arguments (either an explicit collection of values, or a message to #send
|
522
|
+
# to self to obtain such collection).
|
523
|
+
#
|
524
|
+
def SR_transitions *aa, &b
|
525
|
+
return zip_to_hash SR_transitions(), *aa, &b unless aa.empty? && b.nil?
|
526
|
+
sift_from_net :SR_transitions
|
527
|
+
end
|
528
|
+
|
529
|
+
# Like #SR_transitions, except that transition names are used instead of
|
530
|
+
# instances, whenever possible.
|
531
|
+
#
|
532
|
+
def SR_tt *aa, &b
|
533
|
+
return zip_to_hash SR_tt(), *aa, &b unless aa.empty? && b.nil?
|
534
|
+
SR_transitions().map { |t| t.name || t }
|
535
|
+
end
|
536
|
+
|
537
|
+
# ==== Assignment (A) transitions
|
538
|
+
|
539
|
+
# Without arguments or block, it returns simply a list of assignment
|
540
|
+
# transitions. Otherwise, it returns a hash, whose keys are the A
|
541
|
+
# transitions, and whose values are governed by the supplied arguments
|
542
|
+
# (either an explicit collection of values, or a message to #send
|
543
|
+
# to self to obtain such collection).
|
544
|
+
#
|
545
|
+
def A_transitions *aa, &b
|
546
|
+
return zip_to_hash A_transitions(), *aa, &b unless aa.empty? && b.nil?
|
547
|
+
sift_from_net :A_transitions
|
548
|
+
end
|
549
|
+
|
550
|
+
# Like #A_transitions, except that transition names are used instead of
|
551
|
+
# instances, whenever possible.
|
552
|
+
#
|
553
|
+
def A_tt *aa, &b
|
554
|
+
return zip_to_hash A_tt(), *aa, &b unless aa.empty? && b.nil?
|
555
|
+
A_transitions().map { |t| t.name || t }
|
556
|
+
end
|
557
|
+
|
558
|
+
# ==== Stoichiometric transitions of any kind (S transitions)
|
559
|
+
|
560
|
+
# Without arguments or block, it returns simply a list of stoichiometric
|
561
|
+
# transitions. Otherwise, it returns a hash, whose keys are the S
|
562
|
+
# transitions, and whose values are governed by the supplied arguments
|
563
|
+
# (either an explicit collection of values, or a message to #send to
|
564
|
+
# self to obtain such collection).
|
565
|
+
#
|
566
|
+
def S_transitions *aa, &b
|
567
|
+
return zip_to_hash S_transitions(), *aa, &b unless aa.empty? && b.nil?
|
568
|
+
sift_from_net :S_transitions
|
569
|
+
end
|
570
|
+
|
571
|
+
# Like #S_transitions, except that transition names are used instead of
|
572
|
+
# instances, whenever possible.
|
573
|
+
#
|
574
|
+
def S_tt *aa, &b
|
575
|
+
return zip_to_hash S_tt(), *aa, &b unless aa.empty? && b.nil?
|
576
|
+
S_transitions().map { |t| t.name || t }
|
577
|
+
end
|
578
|
+
|
579
|
+
# ==== Nonstoichiometric transitions of any kind (s transitions)
|
580
|
+
|
581
|
+
# Without arguments or block, it returns simply a list of
|
582
|
+
# nonstoichiometric transitions. Otherwise, it returns a hash, whose
|
583
|
+
# keys are the s transitions, and whose values are governed by the
|
584
|
+
# supplied arguments (either an explicit collection of values, or a
|
585
|
+
# message to #send to self to obtain such collection).
|
586
|
+
#
|
587
|
+
def s_transitions *aa, &b
|
588
|
+
return zip_to_hash s_transitions, *aa, &b unless aa.empty? && b.nil?
|
589
|
+
sift_from_net :s_transitions
|
590
|
+
end
|
591
|
+
|
592
|
+
# Like #s_transitions, except that transition names are used instead of
|
593
|
+
# instances, whenever possible.
|
594
|
+
#
|
595
|
+
def s_tt *aa, &b
|
596
|
+
return zip_to_hash s_tt, *aa, &b unless aa.empty? && b.nil?
|
597
|
+
s_transitions.map { |t| t.name || t }
|
598
|
+
end
|
599
|
+
|
600
|
+
# ==== Transitions with rate (R transitions), otherwise of any kind
|
601
|
+
|
602
|
+
# Without arguments or block, it returns simply a list of transitions
|
603
|
+
# with rate. Otherwise, it returns a hash, whose keys are the R
|
604
|
+
# transitions, and whose values are governed by the supplied arguments
|
605
|
+
# (either an explicit collection of values, or a message to #send to
|
606
|
+
# self to obtain such collection).
|
607
|
+
#
|
608
|
+
def R_transitions *aa, &b
|
609
|
+
return zip_to_hash R_transitions(), *aa, &b unless aa.empty? && b.nil?
|
610
|
+
sift_from_net :R_transitions
|
611
|
+
end
|
612
|
+
|
613
|
+
# Like #s_transitions, except that transition names are used instead of
|
614
|
+
# instances, whenever possible.
|
615
|
+
#
|
616
|
+
def R_tt *aa, &b
|
617
|
+
return zip_to_hash R_tt(), *aa, &b unless aa.empty? && b.nil?
|
618
|
+
R_transitions().map { |t| t.name || t }
|
619
|
+
end
|
620
|
+
|
621
|
+
# ==== Rateless transitions (r transitions), otherwise of any kind
|
622
|
+
|
623
|
+
# Without arguments or block, it returns simply a list of rateless
|
624
|
+
# transitions. Otherwise, it returns a hash, whose keys are the r
|
625
|
+
# transitions, and whose values are governed by the supplied arguments
|
626
|
+
# (either an explicit collection of values, or a message to #send to
|
627
|
+
# self to obtain such collection).
|
628
|
+
#
|
629
|
+
def r_transitions *aa, &b
|
630
|
+
return zip_to_hash r_transitions, *aa, &b unless aa.empty? && b.nil?
|
631
|
+
sift_from_net :r_transitions
|
632
|
+
end
|
633
|
+
|
634
|
+
# Like #r_transitions, except that transition names are used instead of
|
635
|
+
# instances, whenever possible.
|
636
|
+
#
|
637
|
+
def r_tt *aa, &b
|
638
|
+
return zip_to_hash r_tt, *aa, &b unless aa.empty? && b.nil?
|
639
|
+
r_transitions.map { |t| t.name || t }
|
640
|
+
end
|
641
|
+
|
642
|
+
# === Methods presenting other simulation assets
|
643
|
+
|
644
|
+
# ==== Regarding ts transitions
|
645
|
+
#
|
646
|
+
# (Their closures supply directly Δ codomain.)
|
647
|
+
|
648
|
+
# Exposing Δ state closures for ts transitions.
|
649
|
+
#
|
650
|
+
attr_reader :Δ_closures_for_ts
|
651
|
+
|
652
|
+
# Delta state contribution if ts transitions fire once. The closures
|
653
|
+
# are called in their order, but the state update is not performed
|
654
|
+
# between the calls (ie. they fire "simultaneously").
|
655
|
+
#
|
656
|
+
def Δ_if_ts_fire_once
|
657
|
+
Δ_closures_for_ts.map( &:call ).reduce( @zero_ᴍ, :+ )
|
658
|
+
end
|
659
|
+
|
660
|
+
# ==== Regarding Tsr transitions
|
661
|
+
#
|
662
|
+
# (Their closures do take Δt as argument, but do not expose their ∂,
|
663
|
+
# and they might not even have one.)
|
664
|
+
|
665
|
+
# Exposing Δ state closures for Tsr transitions.
|
666
|
+
#
|
667
|
+
attr_reader :Δ_closures_for_Tsr
|
668
|
+
|
669
|
+
# Delta state contribution for Tsr transitions given Δt.
|
670
|
+
#
|
671
|
+
def Δ_for_Tsr( Δt )
|
672
|
+
Δ_closures_for_Tsr.map { |cl| cl.( Δt ) }.reduce( @zero_ᴍ, :+ )
|
673
|
+
end
|
674
|
+
|
675
|
+
# ==== Regarding tS transitions
|
676
|
+
#
|
677
|
+
# (These transitions are timeless, but stoichiometric. It means that their
|
678
|
+
# closures do not output Δ state contribution directly, but instead they
|
679
|
+
# output a single number, which is a transition action, and Δ state is then
|
680
|
+
# computed from it by multiplying the the action vector with the
|
681
|
+
# stoichiometry matrix.
|
682
|
+
|
683
|
+
# Exposing action closures for tS transitions.
|
684
|
+
#
|
685
|
+
attr_reader :action_closures_for_tS
|
686
|
+
|
687
|
+
# Action vector for if tS transitions fire once. The closures are called
|
688
|
+
# in their order, but the state update is not performed between the
|
689
|
+
# calls (ie. they fire "simultaneously").
|
690
|
+
#
|
691
|
+
def action_vector_for_tS
|
692
|
+
Matrix.column_vector action_closures_for_tS.map( &:call )
|
693
|
+
end
|
694
|
+
alias α_for_tS action_vector_for_tS
|
695
|
+
|
696
|
+
# Action vector if tS transitions fire once, like the previous method.
|
697
|
+
# But by calling this method, the caller asserts that all timeless
|
698
|
+
# transitions in this simulation are stoichiometric (or error is raised).
|
699
|
+
#
|
700
|
+
def action_vector_for_timeless_transitions
|
701
|
+
return action_vector_for_tS if ts_transitions.empty?
|
702
|
+
raise "The simulation also contains nonstoichiometric timeless " +
|
703
|
+
"transitions! Consider using #action_vector_for_tS."
|
704
|
+
end
|
705
|
+
alias action_vector_for_t action_vector_for_timeless_transitions
|
706
|
+
alias α_for_t action_vector_for_timeless_transitions
|
707
|
+
|
708
|
+
# Δ state contribution for tS transitions.
|
709
|
+
#
|
710
|
+
def Δ_if_tS_fire_once
|
711
|
+
S_for_tS() * action_vector_for_tS
|
712
|
+
end
|
713
|
+
|
714
|
+
# ==== Regarding TSr transitions
|
715
|
+
#
|
716
|
+
# (Same as Tsr, but stoichiometric. That is, their closures do not return
|
717
|
+
# Δ contribution, but transition's action, which is to be multiplied by
|
718
|
+
# the its stoichiometry to obtain Δ contribution.)
|
719
|
+
|
720
|
+
# Exposing action closures for TSr transitions.
|
721
|
+
#
|
722
|
+
attr_reader :action_closures_for_TSr
|
723
|
+
|
724
|
+
# By calling this method, the caller asserts that all timeless transitions
|
725
|
+
# in this simulation are stoichiometric (or error is raised).
|
726
|
+
#
|
727
|
+
def action_closures_for_Tr
|
728
|
+
return action_closures_for_TSr if self.TSr_transitions.empty?
|
729
|
+
raise "The simulation also contains nonstoichiometric timed rateless " +
|
730
|
+
"transitions! Consider using #action_closures_for_TSr."
|
731
|
+
end
|
732
|
+
|
733
|
+
# Action vector for timed rateless stoichiometric transitions.
|
734
|
+
#
|
735
|
+
def action_vector_for_TSr( Δt )
|
736
|
+
Matrix.column_vector action_closures_for_TSr.map { |c| c.( Δt ) }
|
737
|
+
end
|
738
|
+
alias α_for_TSr action_vector_for_TSr
|
739
|
+
|
740
|
+
# Action vector for timed rateless stoichiometric transitions
|
741
|
+
# By calling this method, the caller asserts that all timeless transitions
|
742
|
+
# in this simulation are stoichiometric (or error is raised).
|
743
|
+
#
|
744
|
+
def action_vector_for_Tr( Δt )
|
745
|
+
return action_vector_for_TSr( Δt ) if TSr_transitions().empty?
|
746
|
+
raise "The simulation also contains nonstoichiometric timed rateless " +
|
747
|
+
"transitions! Consider using #action_vector_for_TSr."
|
748
|
+
end
|
749
|
+
alias α_for_Tr action_vector_for_Tr
|
750
|
+
|
751
|
+
# Computes delta state for TSr transitions, given a Δt.
|
752
|
+
#
|
753
|
+
def Δ_for_TSr( Δt )
|
754
|
+
S_for_TSr() * action_vector_for_TSr( Δt )
|
755
|
+
end
|
756
|
+
|
757
|
+
# ==== Regarding sR transitions
|
758
|
+
#
|
759
|
+
# (Whether nonstoichiometric or stoichiometric, transitions with rate
|
760
|
+
# explicitly provide their contribution to the the state differential,
|
761
|
+
# rather than just contribution to the Δ state.)
|
762
|
+
|
763
|
+
# Exposing rate closures for sR transitions.
|
764
|
+
#
|
765
|
+
attr_reader :rate_closures_for_sR
|
766
|
+
|
767
|
+
# By calling this method, the caller asserts that there are no rateless
|
768
|
+
# transitions in the simulation (or error is raised).
|
769
|
+
#
|
770
|
+
def rate_closures_for_nonstoichiometric_transitions
|
771
|
+
return rate_closures_for_sR if r_transitions.empty?
|
772
|
+
raise "The simulation also contains rateless transitions! Consider " +
|
773
|
+
"using #rate_closures_for_sR."
|
774
|
+
end
|
775
|
+
alias rate_closures_for_s rate_closures_for_nonstoichiometric_transitions
|
776
|
+
|
777
|
+
# State differential for sR transitions.
|
778
|
+
#
|
779
|
+
def gradient_for_sR
|
780
|
+
rate_closures_for_sR.map( &:call ).reduce( @zero_ᴍ, :+ )
|
781
|
+
end
|
782
|
+
|
783
|
+
# State differential for sR transitions as a hash { place_name: ∂ / ∂ᴛ }.
|
784
|
+
#
|
785
|
+
def ∂_sR
|
786
|
+
free_pp :gradient_for_sR
|
787
|
+
end
|
788
|
+
|
789
|
+
# While for sR transitions, state differential is what matters the most,
|
790
|
+
# as a conveniece, this method for multiplying the differential by provided
|
791
|
+
# Δt is added.
|
792
|
+
#
|
793
|
+
def Δ_Euler_for_sR( Δt )
|
794
|
+
gradient_for_sR * Δt
|
795
|
+
end
|
796
|
+
alias Δ_euler_for_sR Δ_Euler_for_sR
|
797
|
+
|
798
|
+
# ==== Regarding SR_transitions
|
799
|
+
#
|
800
|
+
# (Whether nonstoichiometric or stoichiometric, transitions with rate
|
801
|
+
# explicitly provide their contribution to the the state differential,
|
802
|
+
# rather than just contribution to the Δ state.)
|
803
|
+
|
804
|
+
# Exposing rate closures for SR transitions.
|
805
|
+
#
|
806
|
+
attr_reader :rate_closures_for_SR
|
807
|
+
|
808
|
+
# Rate closures for SR transitions. By calling this method, the caller
|
809
|
+
# asserts that there are no rateless transitions in the simulation
|
810
|
+
# (or error).
|
811
|
+
#
|
812
|
+
def rate_closures_for_stoichiometric_transitions
|
813
|
+
return rate_closures_for_SR if r_transitions.empty?
|
814
|
+
raise "The simulation also contains rateless transitions! Consider " +
|
815
|
+
"using #rate_closures_for_SR"
|
816
|
+
end
|
817
|
+
alias rate_closures_for_S rate_closures_for_stoichiometric_transitions
|
818
|
+
|
819
|
+
# Rate closures for SR transitions. By calling this method, the caller
|
820
|
+
# asserts that there are only SR transitions in the simulation (or error).
|
821
|
+
#
|
822
|
+
def rate_closures
|
823
|
+
return rate_closures_for_S if s_transitions.empty?
|
824
|
+
raise "The simulation contains also nonstoichiometric transitions! " +
|
825
|
+
"Consider using #rate_closures_for_S."
|
826
|
+
end
|
827
|
+
|
828
|
+
# While rateless stoichiometric transitions provide transition's action as
|
829
|
+
# their closure output, SR transitions' closures return flux, which is
|
830
|
+
# ∂action / ∂t. This methods return flux for SR transitions as a column
|
831
|
+
# vector.
|
832
|
+
#
|
833
|
+
def flux_vector_for_SR
|
834
|
+
Matrix.column_vector rate_closures_for_SR.map( &:call )
|
835
|
+
end
|
836
|
+
alias φ_for_SR flux_vector_for_SR
|
837
|
+
|
838
|
+
# Flux vector for a selected collection of SR transitions.
|
839
|
+
#
|
840
|
+
def flux_vector_for *transitions
|
841
|
+
# TODO
|
842
|
+
end
|
843
|
+
alias φ_for flux_vector_for
|
844
|
+
|
845
|
+
# Flux vector for SR transitions. Same as the previous method, but the
|
846
|
+
# caller asserts that there are only SR transitions in the simulation
|
847
|
+
# (or error).
|
848
|
+
#
|
849
|
+
def flux_vector
|
850
|
+
return flux_vector_for_SR if s_transitions.empty? && r_transitions.empty?
|
851
|
+
raise "One may only call this method when all the transitions of the " +
|
852
|
+
"simulation are SR transitions."
|
853
|
+
end
|
854
|
+
alias φ flux_vector
|
855
|
+
|
856
|
+
# Flux of SR transitions as an array.
|
857
|
+
#
|
858
|
+
def flux_for_SR
|
859
|
+
flux_vector_for_SR.column( 0 ).to_a
|
860
|
+
end
|
861
|
+
|
862
|
+
# Flux for a selected collection of SR transitions.
|
863
|
+
#
|
864
|
+
def flux_for *transitions
|
865
|
+
all = SR_transitions :flux_for_SR
|
866
|
+
transitions.map { |t| transition t }.map { |e| all[e] }
|
867
|
+
end
|
868
|
+
|
869
|
+
# Same as #flux_for_SR, but with caller asserting that there are none but
|
870
|
+
# SR transitions in the simulation (or error).
|
871
|
+
#
|
872
|
+
def flux
|
873
|
+
flux_vector.column( 0 ).to_a
|
874
|
+
end
|
875
|
+
|
876
|
+
# Flux of SR transitions as a hash { name: flux }.
|
877
|
+
#
|
878
|
+
def f_SR
|
879
|
+
SR_tt :flux_for_SR
|
880
|
+
end
|
881
|
+
|
882
|
+
# Flux for a selected collection of SR transition as hash { key => flux }.
|
883
|
+
#
|
884
|
+
def f_for *transitions
|
885
|
+
Hash[ transitions.zip( flux_for *transitions ) ]
|
886
|
+
end
|
887
|
+
|
888
|
+
# Same as #f_SR, but with caller asserting that there are none but SR
|
889
|
+
# transitions in the simulation (or error).
|
890
|
+
#
|
891
|
+
def f
|
892
|
+
SR_tt :flux
|
893
|
+
end
|
894
|
+
|
895
|
+
# State differential for SR transitions.
|
896
|
+
#
|
897
|
+
def gradient_for_SR
|
898
|
+
S_for_SR() * flux_vector_for_SR
|
899
|
+
end
|
900
|
+
|
901
|
+
# State differential for SR transitions as a hash { place_name: ∂ / ∂ᴛ }.
|
902
|
+
#
|
903
|
+
def ∂_SR
|
904
|
+
free_pp :gradient_for_SR
|
905
|
+
end
|
906
|
+
|
907
|
+
# Action vector for SR transitions under an assumption of making an Euler
|
908
|
+
# step, whose size is given by the Δt argument.
|
909
|
+
#
|
910
|
+
def Euler_action_vector_for_SR( Δt )
|
911
|
+
flux_vector_for_SR * Δt
|
912
|
+
end
|
913
|
+
alias euler_action_vector_for_SR Euler_action_vector_for_SR
|
914
|
+
alias Euler_α_for_SR Euler_action_vector_for_SR
|
915
|
+
alias euler_α_for_SR Euler_action_vector_for_SR
|
916
|
+
|
917
|
+
# Euler action fro SR transitions as an array.
|
918
|
+
#
|
919
|
+
def Euler_action_for_SR( Δt )
|
920
|
+
Euler_action_vector_for_SR( Δt ).column( 0 ).to_a
|
921
|
+
end
|
922
|
+
alias euler_action_for_SR Euler_action_for_SR
|
923
|
+
|
924
|
+
# Convenience calculator of Δ state for SR transitions, assuming a single
|
925
|
+
# Euler step with Δt given as argument.
|
926
|
+
#
|
927
|
+
def Δ_Euler_for_SR( Δt )
|
928
|
+
gradient_for_SR * Δt
|
929
|
+
end
|
930
|
+
alias Δ_euler_for_SR Δ_Euler_for_SR
|
931
|
+
|
932
|
+
# Δ state for SR transitions, assuming Euler step with Δt as the argument,
|
933
|
+
# returned as an array.
|
934
|
+
#
|
935
|
+
def Δ_Euler_array_for_SR( Δt )
|
936
|
+
Δ_Euler_for_SR( Δt ).column( 0 ).to_a
|
937
|
+
end
|
938
|
+
alias Δ_euler_array_for_SR Δ_Euler_array_for_SR
|
939
|
+
|
940
|
+
|
941
|
+
# ==== Regarding A transitions
|
942
|
+
#
|
943
|
+
# (Assignment transitions directly replace the values in their codomain
|
944
|
+
# places with their results.)
|
945
|
+
|
946
|
+
# Exposing assignment closures for A transitions.
|
947
|
+
#
|
948
|
+
attr_reader :assignment_closures_for_A
|
949
|
+
|
950
|
+
# Returns the array of places to which the assignment transitions assign.
|
951
|
+
#
|
952
|
+
def A_target_places
|
953
|
+
# TODO
|
954
|
+
end
|
955
|
+
|
956
|
+
# Like #A_target_places, but returns place names.
|
957
|
+
#
|
958
|
+
def A_target_pp
|
959
|
+
# TODO
|
960
|
+
end
|
961
|
+
|
962
|
+
# Returns the assignments as they would if all A transitions fired now,
|
963
|
+
# as a hash { place => assignment }.
|
964
|
+
#
|
965
|
+
def assignments
|
966
|
+
# TODO
|
967
|
+
end
|
968
|
+
|
969
|
+
# Like #assignments, but place names are used instead { name: assignment }.
|
970
|
+
#
|
971
|
+
def a
|
972
|
+
# TODO
|
973
|
+
end
|
974
|
+
|
975
|
+
# Returns the assignments as a column vector.
|
976
|
+
#
|
977
|
+
def A_action
|
978
|
+
Matrix.column_vector( assignments.reduce( free_places { nil } ) do |α, p|
|
979
|
+
α[p] = marking
|
980
|
+
end )
|
981
|
+
# TODO: Assignment action to a clamped place should result in a warning.
|
982
|
+
end
|
983
|
+
|
984
|
+
# ==== Sparse stoichiometry vectors for transitions
|
985
|
+
|
986
|
+
# For the transition specified by the argument, this method returns the
|
987
|
+
# sparse stoichiometry vector corresponding to the free places.
|
988
|
+
#
|
989
|
+
def sparse_σ transition
|
990
|
+
instance = transition( transition )
|
991
|
+
raise AE, "Transition #{transition} not stoichiometric!" unless
|
992
|
+
instance.stoichiometric?
|
993
|
+
Matrix.correspondence_matrix( instance.codomain, free_places ) *
|
994
|
+
Matrix.column_vector( instance.stoichiometry )
|
995
|
+
end
|
996
|
+
|
997
|
+
# For the transition specified by the argument, this method returns the
|
998
|
+
# sparse stoichiometry vector mapped to all the places of the simulation.
|
999
|
+
#
|
1000
|
+
def sparse_stoichiometry_vector transition
|
1001
|
+
instance = transition( transition )
|
1002
|
+
raise AE, "Transition #{transition} not stoichiometric!" unless
|
1003
|
+
instance.stoichiometric?
|
1004
|
+
Matrix.correspondence_matrix( instance.codomain, places ) *
|
1005
|
+
Matrix.column_vector( instance.stoichiometry )
|
1006
|
+
end
|
1007
|
+
|
1008
|
+
# Correspondence matrix free places => all places.
|
1009
|
+
#
|
1010
|
+
attr_reader :F2A
|
1011
|
+
|
1012
|
+
# Correspondence matrix clamped places => all places.
|
1013
|
+
#
|
1014
|
+
attr_reader :C2A
|
1015
|
+
|
1016
|
+
# Produces the inspect string of the transition.
|
1017
|
+
#
|
1018
|
+
def inspect
|
1019
|
+
"#<YPetri::Simulation: #{pp.size} pp, #{tt.size} tt, ID: #{object_id} >"
|
1020
|
+
end
|
1021
|
+
|
1022
|
+
# Produces a string briefly describing the simulation instance.
|
1023
|
+
#
|
1024
|
+
def to_s
|
1025
|
+
"Simulation[#{pp.size} pp, #{tt.size} tt]"
|
1026
|
+
end
|
1027
|
+
|
1028
|
+
private
|
1029
|
+
|
1030
|
+
# This helper method takes a collection, a variable number of other arguments
|
1031
|
+
# and an optional block, and returns a hash whose keys are the collection
|
1032
|
+
# members, and whose values are given by the supplied othe arguments and/or
|
1033
|
+
# block in the following way: If there is no additional argument, but a block
|
1034
|
+
# is supplied, this is applied to the collection. If there is exactly one
|
1035
|
+
# other argument, and it is also a collection, it is used as values.
|
1036
|
+
# Otherwise, these other arguments are treated as a message to be sent to
|
1037
|
+
# self (via #send), expecting it to return a collection to be used as hash
|
1038
|
+
# values. Optional block (which is always assumed to be unary) can be used
|
1039
|
+
# to additionally modify the second collection.
|
1040
|
+
#
|
1041
|
+
def zip_to_hash collection, *args, &block
|
1042
|
+
sz = args.size
|
1043
|
+
values = if sz == 0 then collection
|
1044
|
+
elsif sz == 1 && args[0].respond_to?( :each ) then args[0]
|
1045
|
+
else send *args end
|
1046
|
+
Hash[ collection.zip( block ? values.map( &block ) : values ) ]
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
# Chicken approach towards ensuring that transitions in question come in
|
1050
|
+
# the same order as in @transitions local variable. Takes a symbol as the
|
1051
|
+
# argument (:SR, :TSr, :sr etc.)
|
1052
|
+
#
|
1053
|
+
def sift_from_net type_of_transitions
|
1054
|
+
from_net = net.send type_of_transitions
|
1055
|
+
@transitions.select { |t| from_net.include? t }
|
1056
|
+
end
|
1057
|
+
|
1058
|
+
# Resets the simulation
|
1059
|
+
#
|
1060
|
+
def reset!
|
1061
|
+
puts "Starting #reset! method" if YPetri::DEBUG
|
1062
|
+
# zero_vector = Matrix.column_vector( places.map { SY::ZERO rescue 0 } ) # Float zeros
|
1063
|
+
zero_vector = Matrix.column_vector( places.map { 0 } ) # Float zeros
|
1064
|
+
puts "zero vector prepared" if YPetri::DEBUG
|
1065
|
+
mv_clamped = compute_marking_vector_of_clamped_places
|
1066
|
+
puts "#reset! obtained marking vector of clamped places" if YPetri::DEBUG
|
1067
|
+
clamped_component = C2A() * mv_clamped
|
1068
|
+
puts "clamped component of marking vector prepared:\n#{clamped_component}" if YPetri::DEBUG
|
1069
|
+
mv_free = compute_initial_marking_vector_of_free_places
|
1070
|
+
puts "#reset! obtained initial marking vector of free places" if YPetri::DEBUG
|
1071
|
+
free_component = F2A() * mv_free
|
1072
|
+
puts "free component of marking vector prepared:\n#{free_component}" if YPetri::DEBUG
|
1073
|
+
free_component.aT { |v|
|
1074
|
+
qnt = v.first.quantity rescue :no_quantity
|
1075
|
+
unless qnt == :no_quantity
|
1076
|
+
v.all? { |e| e.quantity == qnt }
|
1077
|
+
else true end
|
1078
|
+
}
|
1079
|
+
puts "free component of marking vector prepared:\n#{free_component}" if YPetri::DEBUG
|
1080
|
+
@marking_vector = zero_vector + clamped_component + free_component
|
1081
|
+
puts "marking vector assembled\n#{m}\n, about to reset recording" if YPetri::DEBUG
|
1082
|
+
reset_recording!
|
1083
|
+
puts "reset recording done, about to initiate sampling process" if YPetri::DEBUG
|
1084
|
+
note_state_change!
|
1085
|
+
puts "sampling process initiated, #reset! done" if YPetri::DEBUG
|
1086
|
+
return self
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
# Resets the recording.
|
1090
|
+
#
|
1091
|
+
def reset_recording!
|
1092
|
+
@recording = {}
|
1093
|
+
end
|
1094
|
+
|
1095
|
+
# To be called whenever the state changes. The method will cogitate, whether
|
1096
|
+
# the observed state change warrants calling #sample!
|
1097
|
+
#
|
1098
|
+
def note_state_change!
|
1099
|
+
sample! # default for vanilla Simulation: sample! at every occasion
|
1100
|
+
end
|
1101
|
+
|
1102
|
+
# Performs sampling. A snapshot of the current simulation state is recorded
|
1103
|
+
# into @recording hash as a pair { sampling_event => simulation state }.
|
1104
|
+
#
|
1105
|
+
def sample! key=L!(:sample!)
|
1106
|
+
@sample_number = @sample_number + 1 rescue 0
|
1107
|
+
@recording[ key.ℓ?(:sample!) ? @sample_number : key ] =
|
1108
|
+
marking.map { |n| n.round SAMPLING_DECIMAL_PLACES }
|
1109
|
+
end
|
1110
|
+
|
1111
|
+
# Called upon initialzation
|
1112
|
+
#
|
1113
|
+
def compute_initial_marking_vector_of_free_places
|
1114
|
+
puts "computing the marking vector of free places" if YPetri::DEBUG
|
1115
|
+
results = free_places.map { |p|
|
1116
|
+
im = @initial_marking[ p ]
|
1117
|
+
puts "doing free place #{p} with init. marking #{im}" if YPetri::DEBUG
|
1118
|
+
# unwrap places / cells
|
1119
|
+
im = case im
|
1120
|
+
when YPetri::Place then im.marking
|
1121
|
+
else im end
|
1122
|
+
case im
|
1123
|
+
when Proc then im.call
|
1124
|
+
else im end
|
1125
|
+
}
|
1126
|
+
# and create the matrix out of the results
|
1127
|
+
puts "about to create the column vector" if YPetri::DEBUG
|
1128
|
+
cv = Matrix.column_vector results
|
1129
|
+
puts "column vector #{cv} prepared" if YPetri::DEBUG
|
1130
|
+
return cv
|
1131
|
+
end
|
1132
|
+
|
1133
|
+
# Called upon initialization
|
1134
|
+
#
|
1135
|
+
def compute_marking_vector_of_clamped_places
|
1136
|
+
puts "computing the marking vector of clamped places" if YPetri::DEBUG
|
1137
|
+
results = clamped_places.map { |p|
|
1138
|
+
clamp = @place_clamps[ p ]
|
1139
|
+
puts "doing clamped place #{p} with clamp #{clamp}" if YPetri::DEBUG
|
1140
|
+
# unwrap places / cells
|
1141
|
+
clamp = case clamp
|
1142
|
+
when YPetri::Place then clamp.marking
|
1143
|
+
else clamp end
|
1144
|
+
# unwrap closure by calling it
|
1145
|
+
case clamp
|
1146
|
+
when Proc then clamp.call
|
1147
|
+
else clamp end
|
1148
|
+
}
|
1149
|
+
# and create the matrix out of the results
|
1150
|
+
puts "about to create the column vector" if YPetri::DEBUG
|
1151
|
+
cv = Matrix.column_vector results
|
1152
|
+
puts "column vector #{cv} prepared" if YPetri::DEBUG
|
1153
|
+
return cv
|
1154
|
+
end
|
1155
|
+
|
1156
|
+
# Expects a Δ marking vector for free places and performs the specified
|
1157
|
+
# change on the marking vector for all places.
|
1158
|
+
#
|
1159
|
+
def update_marking! Δ_free_places
|
1160
|
+
@marking_vector += F2A() * Δ_free_places
|
1161
|
+
end
|
1162
|
+
|
1163
|
+
# Fires all assignment transitions once.
|
1164
|
+
#
|
1165
|
+
def assignment_transitions_all_fire!
|
1166
|
+
assignment_closures_for_A.each do |closure|
|
1167
|
+
@marking_vector = closure.call # TODO: This offers better algorithm.
|
1168
|
+
end
|
1169
|
+
end
|
1170
|
+
alias A_all_fire! assignment_transitions_all_fire!
|
1171
|
+
|
1172
|
+
# ----------------------------------------------------------------------
|
1173
|
+
# Methods to create other instance assets upon initialization.
|
1174
|
+
# These instance assets are created at the beginning, so the work
|
1175
|
+
# needs to be performed only once in the instance lifetime.
|
1176
|
+
|
1177
|
+
def create_Δ_closures_for_ts
|
1178
|
+
ts_transitions.map { |t|
|
1179
|
+
p2d = Matrix.correspondence_matrix( places, t.domain )
|
1180
|
+
c2f = Matrix.correspondence_matrix( t.codomain, free_places )
|
1181
|
+
λ { c2f * t.action_closure.( *( p2d * marking_vector ).column_to_a ) }
|
1182
|
+
}
|
1183
|
+
end
|
1184
|
+
|
1185
|
+
def create_Δ_closures_for_Tsr
|
1186
|
+
Tsr_transitions().map { |t|
|
1187
|
+
p2d = Matrix.correspondence_matrix( places, t.domain )
|
1188
|
+
c2f = Matrix.correspondence_matrix( t.codomain, free_places )
|
1189
|
+
λ { |Δt| c2f * t.action_closure.( Δt, *( p2d * marking_vector ).column_to_a ) }
|
1190
|
+
}
|
1191
|
+
end
|
1192
|
+
|
1193
|
+
def create_action_closures_for_tS
|
1194
|
+
tS_transitions.map{ |t|
|
1195
|
+
p2d = Matrix.correspondence_matrix( places, t.domain )
|
1196
|
+
λ { t.action_closure.( *( p2d * marking_vector ).column_to_a ) }
|
1197
|
+
}
|
1198
|
+
end
|
1199
|
+
|
1200
|
+
def create_action_closures_for_TSr
|
1201
|
+
TSr_transitions().map{ |t|
|
1202
|
+
p2d = Matrix.correspondence_matrix( places, t.domain )
|
1203
|
+
λ { |Δt| t.action_closure.( Δt, *( p2d * marking_vector ).column_to_a ) }
|
1204
|
+
}
|
1205
|
+
end
|
1206
|
+
|
1207
|
+
def create_rate_closures_for_sR
|
1208
|
+
sR_transitions.map{ |t|
|
1209
|
+
p2d = Matrix.correspondence_matrix( places, t.domain )
|
1210
|
+
c2f = Matrix.correspondence_matrix( t.codomain, free_places )
|
1211
|
+
λ { c2f * t.rate_closure.( *( p2d * marking_vector ).column_to_a ) }
|
1212
|
+
}
|
1213
|
+
end
|
1214
|
+
|
1215
|
+
def create_rate_closures_for_SR
|
1216
|
+
SR_transitions().map{ |t|
|
1217
|
+
p2d = Matrix.correspondence_matrix( places, t.domain )
|
1218
|
+
puts "Marking is #{pp :marking rescue nil}" if YPetri::DEBUG
|
1219
|
+
λ { t.rate_closure.( *( p2d * marking_vector ).column_to_a ) }
|
1220
|
+
}
|
1221
|
+
end
|
1222
|
+
|
1223
|
+
def create_assignment_closures_for_A
|
1224
|
+
nils = places.map { nil }
|
1225
|
+
A_transitions().map { |t|
|
1226
|
+
p2d = Matrix.correspondence_matrix( places, t.domain )
|
1227
|
+
c2f = Matrix.correspondence_matrix( t.codomain, free_places )
|
1228
|
+
zero_vector = Matrix.column_vector( places.map { 0 } )
|
1229
|
+
probe = Matrix.column_vector( t.codomain.size.times.map { |a| a + 1 } )
|
1230
|
+
result = ( F2A() * c2f * probe ).column_to_a.map { |n| n == 0 ? nil : n }
|
1231
|
+
assignment_addresses = probe.column_to_a.map { |i| result.index i }
|
1232
|
+
lambda do
|
1233
|
+
# puts "result is #{result}"
|
1234
|
+
act = Array t.action_closure.( *( p2d * marking_vector ).column_to_a )
|
1235
|
+
# puts "assignment addresses are #{assignment_addresses}"
|
1236
|
+
# puts "act is #{act}"
|
1237
|
+
# puts "nils are #{nils}"
|
1238
|
+
assign = assignment_addresses.zip( act )
|
1239
|
+
# puts "assign is #{assign}"
|
1240
|
+
assign = assign.each_with_object nils.dup do |pair, o| o[pair[0]] = pair[1] end
|
1241
|
+
# puts "assign is #{assign}"
|
1242
|
+
@marking_vector.map { |original_marking|
|
1243
|
+
assignment_order = assign.shift
|
1244
|
+
assignment_order ? assignment_order : original_marking
|
1245
|
+
}
|
1246
|
+
end
|
1247
|
+
} # map
|
1248
|
+
end
|
1249
|
+
|
1250
|
+
# Set the marking vector.
|
1251
|
+
#
|
1252
|
+
def set_marking_vector marking_vect
|
1253
|
+
@marking_vector = marking_vect
|
1254
|
+
return self
|
1255
|
+
end
|
1256
|
+
|
1257
|
+
# Set the marking vector (array argument).
|
1258
|
+
#
|
1259
|
+
def set_marking marking_array
|
1260
|
+
set_marking_vector Matrix.column_vector( marking_array )
|
1261
|
+
end
|
1262
|
+
|
1263
|
+
# Set the marking vector (hash argument).
|
1264
|
+
#
|
1265
|
+
def set_pm marking_hash
|
1266
|
+
to_set = place_marking.merge( marking_hash.with_keys do |k| place k end )
|
1267
|
+
set_marking( places.map { |pl| to_set[ pl ] } )
|
1268
|
+
end
|
1269
|
+
|
1270
|
+
# Set the marking vector (array argument, free places).
|
1271
|
+
#
|
1272
|
+
def set_m marking_array_for_free_places
|
1273
|
+
set_pm( free_places( marking_array_for_free_places ) )
|
1274
|
+
end
|
1275
|
+
|
1276
|
+
# Set the marking vector (free places).
|
1277
|
+
#
|
1278
|
+
def set_ᴍ marking_vector_for_free_places
|
1279
|
+
set_m( marking_vector_for_free_places.column_to_a )
|
1280
|
+
end
|
1281
|
+
|
1282
|
+
# Private method for resetting recording.
|
1283
|
+
#
|
1284
|
+
def set_recording rec
|
1285
|
+
@recording = Hash[ rec ]
|
1286
|
+
return self
|
1287
|
+
end
|
1288
|
+
|
1289
|
+
# Duplicate creation. TODO: Perhaps this should be a #dup / #clone method?
|
1290
|
+
#
|
1291
|
+
def duplicate
|
1292
|
+
instance = self.class.new( { method: @method,
|
1293
|
+
net: @net,
|
1294
|
+
place_clamps: @place_clamps,
|
1295
|
+
initial_marking: @initial_marking
|
1296
|
+
}.update( simulation_settings ) )
|
1297
|
+
instance.send :set_recording, recording
|
1298
|
+
instance.send :set_marking_vector, @marking_vector
|
1299
|
+
return instance
|
1300
|
+
end
|
1301
|
+
|
1302
|
+
# Place, Transition, Net class
|
1303
|
+
#
|
1304
|
+
def Place; YPetri::Place end
|
1305
|
+
def Transition; YPetri::Transition end
|
1306
|
+
|
1307
|
+
# Instance identification methods.
|
1308
|
+
#
|
1309
|
+
def place( which ); Place().instance( which ) end
|
1310
|
+
def transition( which ); Transition().instance( which ) end
|
1311
|
+
|
1312
|
+
# LATER: Mathods for timeless simulation.
|
1313
|
+
end # class YPetri::Simulation
|