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