y_petri 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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