y_petri 2.0.3 → 2.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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