y_petri 2.0.3 → 2.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -50,150 +50,110 @@ class YPetri::Simulation
50
50
  end
51
51
  end
52
52
 
53
- # Currently, a simulation instance is largely immutable. It means that
54
- # the net, initial marking, clamps and simulation settings have to be
55
- # supplied upon initialization, whereupon the simulation forms their
56
- # "mental image", which does not change anymore, regardless of what happens
57
- # to the original net and other objects. Required constructor parameters
58
- # are :net, :place_clamps (alias :marking_clamps) and :initial_marking
59
- # (alias :initial_marking_vector). (Simulation subclasses may require other
60
- # arguments in addition to the ones just named.)
61
- #
62
- def initialize args={}
53
+ # Currently, simulation is largely immutable. Net, initial marking, clamps
54
+ # and simulation settings are set upon initialization, whereupon the instance
55
+ # forms their "mental image", which remains immune to any subsequent changes
56
+ # to the original objects. Required parameters are :net, :marking_clamps, and
57
+ # :initial_marking. Optional is :method (simulation method), and :guarded
58
+ # (true/false, whether the simulation guards the transition function results).
59
+ # Guard conditions can be either implicit (guarding against negative values
60
+ # and against type changes in by transition action), or explicitly associated
61
+ # with either places, or transition function results.
62
+ #
63
+ def initialize( method: default_simulation_method,
64
+ guarded: false,
65
+ net: raise( ArgumentError, "Net argument absent!" ),
66
+ marking_clamps: {},
67
+ initial_marking: {} )
63
68
  puts "starting to set up Simulation" if YPetri::DEBUG
64
-
65
- args.may_have :method, syn!: :simulation_method
66
- args.must_have :net do |o| o.class_complies? ::YPetri::Net end
67
- args.may_have :place_clamps, syn!: :marking_clamps
68
- args.may_have :initial_marking, syn!: :initial_marking_vector
69
-
70
- # ==== Simulation method
71
- #
72
- @method = args[:method] || default_simulation_method()
73
-
74
- # ==== Net
75
- #
76
- @net = args[:net].dup # @immutable within the instance
77
- @places = @net.places.dup
78
- @transitions = @net.transitions.dup
79
-
69
+ @method, @guarded, @net = method, guarded, net
70
+ @places, @transitions = @net.places.dup, @net.transitions.dup
80
71
  self.singleton_class.class_exec {
81
72
  define_method :Place do net.send :Place end
82
73
  define_method :Transition do net.send :Transition end
83
74
  define_method :Net do net.send :Net end
84
75
  private :Place, :Transition, :Net
85
- }
86
-
87
- puts "setup of :net mental image complete" if YPetri::DEBUG
88
-
89
- # ==== Simulation parameters
90
- #
91
- # A simulation distinguishes between free and clamped places. For free
92
- # places, initial value has to be specified. For clamped places, clamps
93
- # have to be specified. Both initial values and clamps are expected as
94
- # hash-type named parameters:
95
- @place_clamps = ( args[:place_clamps] || {} ).with_keys { |k| place k }
96
- @initial_marking = ( args[:initial_marking] || {} ).with_keys { |k| place k }
76
+ }; puts "setup of :net mental image complete" if YPetri::DEBUG
97
77
 
78
+ # A simulation distinguishes between free and clamped places. For free
79
+ # places, initial marking has to be specified. For clamped places, marking
80
+ # clamps have to be specified. Both come as hashes:
81
+ @marking_clamps = marking_clamps.with_keys { |k| place k }
82
+ @initial_marking = initial_marking.with_keys { |k| place k }
98
83
  # Enforce that keys in the hashes must be unique:
99
- @place_clamps.keys.aT_equal @place_clamps.keys.uniq
84
+ @marking_clamps.keys.aT_equal @marking_clamps.keys.uniq
100
85
  @initial_marking.keys.aT_equal @initial_marking.keys.uniq
101
-
102
86
  puts "setup of clamps and initial marking done" if YPetri::DEBUG
103
87
 
104
- # === Consistency check
105
- #
106
- # # Clamped places must not have explicit initial marking specified:
107
- # @place_clamps.keys.each { |place|
108
- # place.aT_not "clamped place #{place}",
109
- # "have explicitly specified initial marking" do |place|
110
- # @initial_marking.keys.include? place
111
- # end
112
- # }
113
-
114
- # Each place must be treated: either clamped, or have initial marking
115
- places.each { |p|
116
- p.aT "place #{p}", "have either clamp or initial marking" do |p|
117
- @place_clamps.keys.include?( p ) || @initial_marking.keys.include?( p )
88
+ # Each place must have either clamp, or initial marking:
89
+ places.each { |pl|
90
+ pl.aT "place #{pl}", "have either clamp or initial marking" do |pl|
91
+ ( @marking_clamps.keys + @initial_marking.keys ).include? pl
118
92
  end
119
- }
93
+ }; puts "clamp || initial marking test passed" if YPetri::DEBUG
120
94
 
121
- puts "consistency check for clamps and initial marking passed" if YPetri::DEBUG
122
-
123
- # === Correspondence matrices.
124
-
125
- # Multiplying this matrix by marking vector for free places (ᴍ) gives
126
- # ᴍ mapped for all places.
95
+ # @F2A * (marking vector of free places) maps ᴍ to all places.
127
96
  @F2A = Matrix.correspondence_matrix( free_places, places )
128
-
129
- # Multiplying this matrix by marking vector for clamped places maps that
130
- # vector to all places.
97
+ # @C2A * marking_vector_of_clamped_places maps it to all places.
131
98
  @C2A = Matrix.correspondence_matrix( clamped_places, places )
132
-
133
99
  puts "correspondence matrices set up" if YPetri::DEBUG
134
100
 
135
- # --- Stoichiometry matrices ----
101
+ # Stoichiometry matrices:
136
102
  @S_for_tS = S_for tS_transitions()
137
103
  @S_for_SR = S_for SR_transitions()
138
104
  @S_for_TSr = S_for TSr_transitions()
139
-
140
105
  puts "stoichiometry matrices set up" if YPetri::DEBUG
141
106
 
142
- # ----- Create other assets -----
143
- _closures_for_ts = create_Δ_closures_for_ts
107
+ # Other assets:
108
+ _closures_for_tsa = create_Δ_closures_for_tsa
144
109
  @Δ_closures_for_Tsr = create_Δ_closures_for_Tsr
145
110
  @action_closures_for_tS = create_action_closures_for_tS
146
111
  @action_closures_for_TSr = create_action_closures_for_TSr
147
112
  @rate_closures_for_sR = create_rate_closures_for_sR
148
113
  @rate_closures_for_SR = create_rate_closures_for_SR
149
-
150
114
  @assignment_closures_for_A = create_assignment_closures_for_A
151
-
152
- puts "other assets set up, about to reset" if YPetri::DEBUG
153
-
154
- # ----------- Reset -------------
155
- reset!
156
-
157
115
  @zero_ᴍ = compute_initial_marking_vector_of_free_places.map { |e| e * 0 }
158
116
  @zero_gradient = @zero_ᴍ.dup
117
+ puts "other assets set up, about to reset" if YPetri::DEBUG
159
118
 
160
- puts "reset complete" if YPetri::DEBUG
161
- end
162
-
163
- # Returns a new instance of the system at a different state, while leaving
164
- # all other simulation settings unchanged. This desired state can be
165
- # specified either as marking vector (:ᴍ), marking array of free places (:m),
166
- # marking array of all places (:marking), or marking hash (:pm). In case of
167
- # marking hash, it does not have to be given for each place, missing places
168
- # will be left unchanged.
169
- #
170
- def at *args
171
- oo = args.extract_options!
172
- oo.may_have :m # marking of free places
173
- oo.may_have :marking # marking of all places
174
- oo.may_have :ᴍ, syn!: :m_vector # marking vector of free places
175
- oo.may_have :marking_vector # marking vector of all places
176
- oo.may_have :pm, syn!: [ :p_m, :pmarking, :p_marking, # marking hash
177
- :place_m, :place_marking ]
178
- if oo.has? :marking_vector then
179
- duplicate.send :set_marking_vector, oo.delete( :marking_vector )
180
- elsif oo.has? :marking then
181
- duplicate.send :set_marking, oo.delete( :marking )
182
- elsif oo.has? :m then
183
- duplicate.send :set_m, oo.delete( :m )
184
- elsif oo.has? :ᴍ then
185
- duplicate.send :set_ᴍ, oo.delete( :ᴍ )
186
- elsif oo.has? :pm then
187
- duplicate.send :set_pm, oo.delete( :pm )
188
- else
189
- duplicate.send :set_pm, oo
190
- end
119
+ reset!; puts "reset complete" if YPetri::DEBUG
120
+ end
121
+
122
+ # Returns a new instance of the system simulation at a specified state, with
123
+ # same simulation settings. This state (:marking argument) can be specified
124
+ # either as marking vector for free or all places, marking array for free or
125
+ # all places, or marking hash. If vector or array is given, its size must
126
+ # correspond to the number of either free, or all places. If hash is given,
127
+ # it is not necessary to specify marking of every place – marking of those
128
+ # left out will be left same as in the current state.
129
+ #
130
+ def at( marking: marking, **oo )
131
+ err_msg = "Size of supplied marking must match either the number of " +
132
+ "free places, or the number of all places!"
133
+ update_method = case marking
134
+ when Hash then :update_marking_from_a_hash
135
+ when Matrix then
136
+ case marking.column_to_a.size
137
+ when places.size then :set_marking_vector
138
+ when free_places.size then :set_ᴍ
139
+ else raise TypeError, err_msg end
140
+ else # marking assumed to be an array
141
+ case marking.size
142
+ when places.size then :set_marking
143
+ when free_places.size then :set_m
144
+ else raise TypeError, err_msg end
145
+ end
146
+ return dup( **oo ).send( update_method, marking )
191
147
  end
192
148
 
193
149
  # Exposing @net.
194
150
  #
195
151
  attr_reader :net
196
152
 
153
+ # Is the simulation guarded?
154
+ #
155
+ def guarded?; @guarded end
156
+
197
157
  # Without arguments or block, it returns simply a list of places. Otherwise,
198
158
  # it returns a has whose keys are the places, and whose values are governed
199
159
  # by the supplied parameters (either another collection, or message to #send
@@ -287,7 +247,7 @@ class YPetri::Simulation
287
247
  #
288
248
  def clamped_places *aa, &b
289
249
  return zip_to_hash clamped_places, *aa, &b unless aa.empty? && b.nil?
290
- kk = @place_clamps.keys
250
+ kk = @marking_clamps.keys
291
251
  places.select { |p| kk.include? p }
292
252
  end
293
253
 
@@ -301,9 +261,10 @@ class YPetri::Simulation
301
261
 
302
262
  # Place clamp definitions for clamped places (array)
303
263
  #
304
- def place_clamps
305
- clamped_places.map { |p| @place_clamps[p] }
264
+ def marking_clamps
265
+ clamped_places.map { |p| @marking_clamps[p] }
306
266
  end
267
+ alias place_clamps marking_clamps
307
268
 
308
269
  # Marking array of free places.
309
270
  #
@@ -438,6 +399,26 @@ class YPetri::Simulation
438
399
  ts_transitions.map { |t| t.name || t }
439
400
  end
440
401
 
402
+ # Assignment transitions (A transition) can be regarded as a special kind
403
+ # of ts transition (subtracting away the current marking of their domain
404
+ # and replacing it with the result of their function). But it may often
405
+ # be useful to exclude A transitions from among the ts transitions, and
406
+ # such set is called tsa transitions (timeless nonstoichiometric
407
+ # nonassignment transitions).
408
+ #
409
+ def tsa_transitions *aa, &b
410
+ return zip_to_hash tsa_transitions, *aa, &b unless aa.empty? && b.nil?
411
+ sift_from_net :tsa_transitions
412
+ end
413
+
414
+ # Like #tsa_transitions, except that transition names are used instead of
415
+ # instance, whenever possible.
416
+ #
417
+ def tsa_tt *aa, &b
418
+ return zip_to_hash tsa_tt, *aa, &b unless aa.empty? && b.nil?
419
+ tsa_transitions.map { |t| t.name || t }
420
+ end
421
+
441
422
  # ==== 2. Exposing tS transitions
442
423
 
443
424
  # Without arguments or block, it returns simply a list of timeless
@@ -555,6 +536,7 @@ class YPetri::Simulation
555
536
  return zip_to_hash A_transitions(), *aa, &b unless aa.empty? && b.nil?
556
537
  sift_from_net :A_transitions
557
538
  end
539
+ alias assignment_transitions A_transitions
558
540
 
559
541
  # Like #A_transitions, except that transition names are used instead of
560
542
  # instances, whenever possible.
@@ -563,6 +545,7 @@ class YPetri::Simulation
563
545
  return zip_to_hash A_tt(), *aa, &b unless aa.empty? && b.nil?
564
546
  A_transitions().map { |t| t.name || t }
565
547
  end
548
+ alias assignment_tt A_tt
566
549
 
567
550
  # ==== Stoichiometric transitions of any kind (S transitions)
568
551
 
@@ -656,14 +639,18 @@ class YPetri::Simulation
656
639
 
657
640
  # Exposing Δ state closures for ts transitions.
658
641
  #
659
- attr_reader :Δ_closures_for_ts
642
+ attr_reader :Δ_closures_for_tsa
660
643
 
661
- # Delta state contribution if ts transitions fire once. The closures
662
- # are called in their order, but the state update is not performed
663
- # between the calls (ie. they fire "simultaneously").
644
+ # Delta state contribution if timed nonstoichiometric non-assignment (tsa)
645
+ # transitions fire once. The closures are called in their order, but the state
646
+ # update is not performed between the calls (they fire simultaneously).
647
+ #
648
+ # Note: 'a' in 'tsa' is needed because A (assignment) transitions can also be
649
+ # regarded as a special kind of ts transitions, while they obviously do not
650
+ # act through Δ state, but rather directly enforce marking of their codomain.
664
651
  #
665
- def Δ_if_ts_fire_once
666
- Δ_closures_for_ts.map( &:call ).reduce( @zero_ᴍ, :+ )
652
+ def Δ_if_tsa_fire_once
653
+ Δ_closures_for_tsa.map( &:call ).reduce( @zero_ᴍ, :+ )
667
654
  end
668
655
 
669
656
  # ==== Regarding Tsr transitions
@@ -858,7 +845,8 @@ class YPetri::Simulation
858
845
  def flux_vector
859
846
  return flux_vector_for_SR if s_transitions.empty? && r_transitions.empty?
860
847
  raise "One may only call this method when all the transitions of the " +
861
- "simulation are SR transitions."
848
+ "simulation are SR transitions. Try #flux_vector_for( *transitions ), " +
849
+ "#flux_vector_for_SR, #flux_for( *transitions ), or #flux_for_SR"
862
850
  end
863
851
  alias φ flux_vector
864
852
 
@@ -1154,7 +1142,7 @@ class YPetri::Simulation
1154
1142
  def compute_marking_vector_of_clamped_places
1155
1143
  puts "computing the marking vector of clamped places" if YPetri::DEBUG
1156
1144
  results = clamped_places.map { |p|
1157
- clamp = @place_clamps[ p ]
1145
+ clamp = @marking_clamps[ p ]
1158
1146
  puts "doing clamped place #{p} with clamp #{clamp}" if YPetri::DEBUG
1159
1147
  # unwrap places / cells
1160
1148
  clamp = case clamp
@@ -1179,10 +1167,17 @@ class YPetri::Simulation
1179
1167
  @marking_vector += F2A() * Δ_free_places
1180
1168
  end
1181
1169
 
1170
+ # Guards proposed marking delta.
1171
+ #
1172
+ def guard_Δ! Δ_free_places
1173
+ ary = ( marking_vector + F2A() * Δ_free_places ).column_to_a
1174
+ places.zip( ary ).each { |pl, proposed_m| pl.guard.( proposed_m ) }
1175
+ end
1176
+
1182
1177
  # Fires all assignment transitions once.
1183
1178
  #
1184
1179
  def assignment_transitions_all_fire!
1185
- assignment_closures_for_A.each do |closure|
1180
+ assignment_closures_for_A.each_with_index do |closure, i|
1186
1181
  @marking_vector = closure.call # TODO: This offers better algorithm.
1187
1182
  end
1188
1183
  end
@@ -1193,33 +1188,68 @@ class YPetri::Simulation
1193
1188
  # These instance assets are created at the beginning, so the work
1194
1189
  # needs to be performed only once in the instance lifetime.
1195
1190
 
1196
- def create_Δ_closures_for_ts
1197
- ts_transitions.map { |t|
1191
+ def create_Δ_closures_for_tsa
1192
+ tsa_transitions.map { |t|
1198
1193
  p2d = Matrix.correspondence_matrix( places, t.domain )
1199
1194
  c2f = Matrix.correspondence_matrix( t.codomain, free_places )
1200
- λ { c2f * t.action_closure.( *( p2d * marking_vector ).column_to_a ) }
1195
+ if guarded? then
1196
+ -> {
1197
+ domain_marking = ( p2d * marking_vector ).column_to_a
1198
+ # I. TODO: t.domain_guard.( domain_marking )
1199
+ codomain_change = Array t.action_closure.( *domain_marking )
1200
+ # II. TODO: t.action_guard.( codomain_change )
1201
+ c2f * codomain_change
1202
+ }
1203
+ else
1204
+ -> { c2f * t.action_closure.( *( p2d * marking_vector ).column_to_a ) }
1205
+ end
1201
1206
  }
1202
1207
  end
1203
1208
 
1209
+ def blame_tsa( marking_vect )
1210
+ # If, in spite of passing domain guard and action guard, marking guard
1211
+ # indicates an exception, the method here serves to find the candidate
1212
+ # transitions to blame for the exception, given certain place marking.
1213
+ msg = "Action closure of transition #%{t} with domain #%{dm} and " +
1214
+ "codomain #%{cdm} returns #%{retval} which, when added to place " +
1215
+ "#{p}, gives marking that would flunk place's marking guard."
1216
+ tsa_transitions.each { |t|
1217
+ p2d = Matrix.correspondence_matrix( places, t.domain )
1218
+ rslt = Array t.action_closure( *( p2d * marking_vect ).column_to_a )
1219
+ t.codomain.zip( rslt ).each { |place, Δ|
1220
+ fields = {
1221
+ t: t.name || t.object_id, p: place,
1222
+ dm: Hash[ t.domain_pp.zip domain_marking ],
1223
+ cdm: Hash[ t.codomain_pp.zip( t.codomain.map { |p| ꜧ[p] } ) ],
1224
+ retval: Hash[ t.codomain_pp.zip( codomain_change ) ]
1225
+ }
1226
+ rslt = ꜧ[place] + Δ
1227
+ raise TypeError, msg % fields unless place.marking_guard.( rslt )
1228
+ }
1229
+ }
1230
+ # TODO: Here, #blame_tsa simply raises. It would be however more correct
1231
+ # to gather all blame candidates and present them to the user all.
1232
+ end
1233
+
1204
1234
  def create_Δ_closures_for_Tsr
1205
1235
  Tsr_transitions().map { |t|
1206
1236
  p2d = Matrix.correspondence_matrix( places, t.domain )
1207
1237
  c2f = Matrix.correspondence_matrix( t.codomain, free_places )
1208
- λ { |Δt| c2f * t.action_closure.( Δt, *( p2d * marking_vector ).column_to_a ) }
1238
+ -> Δt { c2f * t.action_closure.( Δt, *( p2d * marking_vector ).column_to_a ) }
1209
1239
  }
1210
1240
  end
1211
1241
 
1212
1242
  def create_action_closures_for_tS
1213
1243
  tS_transitions.map{ |t|
1214
1244
  p2d = Matrix.correspondence_matrix( places, t.domain )
1215
- λ { t.action_closure.( *( p2d * marking_vector ).column_to_a ) }
1245
+ -> { t.action_closure.( *( p2d * marking_vector ).column_to_a ) }
1216
1246
  }
1217
1247
  end
1218
1248
 
1219
1249
  def create_action_closures_for_TSr
1220
1250
  TSr_transitions().map{ |t|
1221
1251
  p2d = Matrix.correspondence_matrix( places, t.domain )
1222
- λ { t| t.action_closure.( Δt, *( p2d * marking_vector ).column_to_a ) }
1252
+ -> Δt { t.action_closure.( Δt, *( p2d * marking_vector ).column_to_a ) }
1223
1253
  }
1224
1254
  end
1225
1255
 
@@ -1227,15 +1257,17 @@ class YPetri::Simulation
1227
1257
  sR_transitions.map{ |t|
1228
1258
  p2d = Matrix.correspondence_matrix( places, t.domain )
1229
1259
  c2f = Matrix.correspondence_matrix( t.codomain, free_places )
1230
- λ { c2f * t.rate_closure.( *( p2d * marking_vector ).column_to_a ) }
1260
+ -> { c2f * t.rate_closure.( *( p2d * marking_vector ).column_to_a ) }
1231
1261
  }
1232
1262
  end
1233
1263
 
1234
1264
  def create_rate_closures_for_SR
1235
- SR_transitions().map{ |t|
1265
+ SR_transitions().map { |t|
1236
1266
  p2d = Matrix.correspondence_matrix( places, t.domain )
1237
1267
  puts "Marking is #{pp :marking rescue nil}" if YPetri::DEBUG
1238
- λ { t.rate_closure.( *( p2d * marking_vector ).column_to_a ) }
1268
+ -> { t.rate_closure.( *( p2d * marking_vector ).column_to_a )
1269
+ .tap do |r| fail YPetri::GuardError, "SR #{t.name}!!!!" if r.is_a? Complex end
1270
+ }
1239
1271
  }
1240
1272
  end
1241
1273
 
@@ -1248,51 +1280,47 @@ class YPetri::Simulation
1248
1280
  probe = Matrix.column_vector( t.codomain.size.times.map { |a| a + 1 } )
1249
1281
  result = ( F2A() * c2f * probe ).column_to_a.map { |n| n == 0 ? nil : n }
1250
1282
  assignment_addresses = probe.column_to_a.map { |i| result.index i }
1251
- lambda do
1252
- # puts "result is #{result}"
1283
+ -> {
1253
1284
  act = Array t.action_closure.( *( p2d * marking_vector ).column_to_a )
1254
- # puts "assignment addresses are #{assignment_addresses}"
1255
- # puts "act is #{act}"
1256
- # puts "nils are #{nils}"
1257
- assign = assignment_addresses.zip( act )
1258
- # puts "assign is #{assign}"
1259
- assign = assign.each_with_object nils.dup do |pair, o| o[pair[0]] = pair[1] end
1260
- # puts "assign is #{assign}"
1261
- @marking_vector.map { |original_marking|
1262
- assignment_order = assign.shift
1263
- assignment_order ? assignment_order : original_marking
1285
+ act.each_with_index { |e, i|
1286
+ fail YPetri::GuardError, "Assignment transition #{t.name} with " +
1287
+ "domain #{t.domain_pp( domain_marking )} has produced a complex " +
1288
+ "number at output positon #{i} (output was #{act})!" if e.is_a?( Complex ) || i.is_a?( Complex )
1264
1289
  }
1265
- end
1290
+ assign = assignment_addresses.zip( act )
1291
+ .each_with_object nils.dup do |pair, o| o[pair[0]] = pair[1] end
1292
+ marking_vector.map { |orig_val| assign.shift || orig_val }
1293
+ }
1266
1294
  } # map
1267
- end
1295
+ end
1268
1296
 
1269
- # Set the marking vector.
1297
+ # Set marking vector (for all places).
1270
1298
  #
1271
- def set_marking_vector marking_vect
1272
- @marking_vector = marking_vect
1299
+ def set_marking_vector marking_vector
1300
+ @marking_vector = marking_vector
1273
1301
  return self
1274
1302
  end
1275
1303
 
1276
- # Set the marking vector (array argument).
1304
+ # Set marking vector, based on marking array of all places.
1277
1305
  #
1278
1306
  def set_marking marking_array
1279
1307
  set_marking_vector Matrix.column_vector( marking_array )
1280
1308
  end
1281
1309
 
1282
- # Set the marking vector (hash argument).
1310
+ # Update marking vector, based on { place => marking } hash argument.
1283
1311
  #
1284
- def set_pm marking_hash
1312
+ def update_marking_from_a_hash marking_hash
1285
1313
  to_set = place_marking.merge( marking_hash.with_keys do |k| place k end )
1286
1314
  set_marking( places.map { |pl| to_set[ pl ] } )
1287
1315
  end
1288
1316
 
1289
- # Set the marking vector (array argument, free places).
1317
+ # Set marking vector based on marking array of free places.
1290
1318
  #
1291
1319
  def set_m marking_array_for_free_places
1292
- set_pm( free_places( marking_array_for_free_places ) )
1320
+ set_marking_from_a_hash( free_places( marking_array_for_free_places ) )
1293
1321
  end
1294
1322
 
1295
- # Set the marking vector (free places).
1323
+ # Set marking vector based on marking vector of free places.
1296
1324
  #
1297
1325
  def set_ᴍ marking_vector_for_free_places
1298
1326
  set_m( marking_vector_for_free_places.column_to_a )
@@ -1305,28 +1333,23 @@ class YPetri::Simulation
1305
1333
  return self
1306
1334
  end
1307
1335
 
1308
- # Duplicate creation. TODO: Perhaps this should be a #dup / #clone method?
1336
+ # Duplicate creation.
1309
1337
  #
1310
- def duplicate
1311
- instance = self.class.new( { method: @method,
1312
- net: @net,
1313
- place_clamps: @place_clamps,
1314
- initial_marking: @initial_marking
1315
- }.update( simulation_settings ) )
1316
- instance.send :set_recording, recording
1317
- instance.send :set_marking_vector, @marking_vector
1318
- return instance
1338
+ def dup( **oo )
1339
+ self.class.new( oo.reverse_merge!( { method: @method,
1340
+ guarded: @guarded,
1341
+ net: @net,
1342
+ marking_clamps: @marking_clamps,
1343
+ initial_marking: @initial_marking
1344
+ }.update( simulation_settings ) ) )
1345
+ .tap { |instance|
1346
+ instance.send :set_recording, recording
1347
+ instance.send :set_marking_vector, @marking_vector
1348
+ }
1319
1349
  end
1320
1350
 
1321
- # Place, Transition, Net class
1322
- #
1323
- def Place; YPetri::Place end
1324
- def Transition; YPetri::Transition end
1325
-
1326
1351
  # Instance identification methods.
1327
1352
  #
1328
1353
  def place( which ); Place().instance( which ) end
1329
1354
  def transition( which ); Transition().instance( which ) end
1330
-
1331
- # LATER: Mathods for timeless simulation.
1332
1355
  end # class YPetri::Simulation
@@ -1,5 +1,4 @@
1
- #encoding: utf-8
2
-
1
+ # -*- coding: utf-8 -*-
3
2
  # A descendant class of YPetri::Simulation that introduces timekeeping.
4
3
  #
5
4
  class YPetri::TimedSimulation < YPetri::Simulation
@@ -78,17 +77,17 @@ class YPetri::TimedSimulation < YPetri::Simulation
78
77
  # (alias :step), :sampling_period (alias :sampling), and :target_time
79
78
  # named arguments.
80
79
  #
81
- def initialize args={}
82
- args.must_have :step_size, syn!: :step
83
- args.must_have :sampling_period, syn!: :sampling
84
- args.may_have :target_time
85
- args.may_have :initial_time
86
- @step_size = args.delete :step_size
87
- @sampling_period = args.delete :sampling_period
88
- @target_time = args.delete :target_time
89
- @initial_time = args.delete( :initial_time ) ||
80
+ def initialize( **named_args )
81
+ named_args.must_have :step_size, syn!: :step
82
+ named_args.must_have :sampling_period, syn!: :sampling
83
+ named_args.may_have :target_time
84
+ named_args.may_have :initial_time
85
+ @step_size = named_args.delete :step_size
86
+ @sampling_period = named_args.delete :sampling_period
87
+ @target_time = named_args.delete :target_time
88
+ @initial_time = named_args.delete( :initial_time ) ||
90
89
  @target_time.nil? ? nil : @sampling_period * 0 # @target_time.class.zero
91
- super args
90
+ super( **named_args )
92
91
  @zero_gradient = @zero_ᴍ.map { |e| step_size.to_f / step_size * e }
93
92
  end
94
93
  # LATER: transition clamps
@@ -97,11 +96,8 @@ class YPetri::TimedSimulation < YPetri::Simulation
97
96
  # which is set to the required state / time. In addition to the parent class,
98
97
  # this version alseo sets time.
99
98
  #
100
- def at *args
101
- oo = args.extract_options!
102
- duplicate = super *args, oo
103
- t = oo.may_have( :t, syn!: :ᴛ ) and duplicate.send :set_time, t
104
- return duplicate
99
+ def at( time: ᴛ, **oo )
100
+ super( **oo ).tap { |duplicate| duplicate.send :set_time, time }
105
101
  end
106
102
 
107
103
  # At the moment, near alias for #run_to_arget_time!
@@ -157,7 +153,13 @@ class YPetri::TimedSimulation < YPetri::Simulation
157
153
  # affected.
158
154
  #
159
155
  def Euler_step!( Δt=@step_size ) # implicit Euler method
160
- update_marking! Δ_Euler_for_free_places( Δt )
156
+ delta = Δ_Euler_for_free_places( Δt )
157
+ if guarded? then
158
+ guard_Δ! delta
159
+ update_marking! delta
160
+ else
161
+ update_marking! delta
162
+ end
161
163
  update_time! Δt
162
164
  end
163
165
  alias euler_step! Euler_step!
@@ -166,8 +168,15 @@ class YPetri::TimedSimulation < YPetri::Simulation
166
168
  # affected.
167
169
  #
168
170
  def timeless_transitions_all_fire!
169
- update_marking! Δ_if_tS_fire_once + Δ_if_ts_fire_once
170
- assignment_transitions_all_fire!
171
+ try "to update marking" do
172
+ update_marking!( note( "Δ state if tS transitions fire once",
173
+ is: Δ_if_tS_fire_once ) +
174
+ note( "Δ state if tsa transitions fire once",
175
+ is: Δ_if_tsa_fire_once ) )
176
+ end
177
+ try "to fire the assignment transitions" do
178
+ assignment_transitions_all_fire!
179
+ end
171
180
  end
172
181
  alias t_all_fire! timeless_transitions_all_fire!
173
182