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.
- checksums.yaml +4 -4
- data/lib/y_petri/dependency_injection.rb +45 -0
- data/lib/y_petri/manipulator/petri_net_related_methods.rb +26 -7
- data/lib/y_petri/manipulator/simulation_related_methods.rb +4 -4
- data/lib/y_petri/net.rb +30 -21
- data/lib/y_petri/place/arcs.rb +96 -0
- data/lib/y_petri/place/guard.rb +122 -0
- data/lib/y_petri/place.rb +89 -132
- data/lib/y_petri/simulation.rb +191 -168
- data/lib/y_petri/timed_simulation.rb +29 -20
- data/lib/y_petri/transition/arcs.rb +51 -0
- data/lib/y_petri/transition/cocking.rb +32 -0
- data/lib/y_petri/transition/constructor_syntax.rb +378 -0
- data/lib/y_petri/transition.rb +391 -831
- data/lib/y_petri/version.rb +1 -1
- data/lib/y_petri/workspace/parametrized_subclassing.rb +8 -13
- data/lib/y_petri/workspace/simulation_related_methods.rb +13 -11
- data/lib/y_petri.rb +8 -3
- data/test/place_test.rb +83 -0
- data/test/transition_test.rb +325 -0
- data/test/y_petri_test.rb +15 -410
- metadata +12 -2
data/lib/y_petri/simulation.rb
CHANGED
@@ -50,150 +50,110 @@ class YPetri::Simulation
|
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
53
|
-
# Currently,
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
#
|
58
|
-
#
|
59
|
-
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
|
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
|
-
|
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
|
-
@
|
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
|
-
#
|
105
|
-
|
106
|
-
|
107
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
#
|
143
|
-
@Δ
|
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
|
164
|
-
#
|
165
|
-
#
|
166
|
-
#
|
167
|
-
#
|
168
|
-
#
|
169
|
-
#
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
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 = @
|
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
|
305
|
-
clamped_places.map { |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 :Δ
|
642
|
+
attr_reader :Δ_closures_for_tsa
|
660
643
|
|
661
|
-
# Delta state contribution if
|
662
|
-
# are called in their order, but the state
|
663
|
-
# between the calls (
|
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 Δ
|
666
|
-
Δ
|
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 = @
|
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.
|
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_Δ
|
1197
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
1252
|
-
# puts "result is #{result}"
|
1283
|
+
-> {
|
1253
1284
|
act = Array t.action_closure.( *( p2d * marking_vector ).column_to_a )
|
1254
|
-
|
1255
|
-
|
1256
|
-
|
1257
|
-
|
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
|
-
|
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
|
-
|
1295
|
+
end
|
1268
1296
|
|
1269
|
-
# Set
|
1297
|
+
# Set marking vector (for all places).
|
1270
1298
|
#
|
1271
|
-
def set_marking_vector
|
1272
|
-
@marking_vector =
|
1299
|
+
def set_marking_vector marking_vector
|
1300
|
+
@marking_vector = marking_vector
|
1273
1301
|
return self
|
1274
1302
|
end
|
1275
1303
|
|
1276
|
-
# Set
|
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
|
-
#
|
1310
|
+
# Update marking vector, based on { place => marking } hash argument.
|
1283
1311
|
#
|
1284
|
-
def
|
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
|
1317
|
+
# Set marking vector based on marking array of free places.
|
1290
1318
|
#
|
1291
1319
|
def set_m marking_array_for_free_places
|
1292
|
-
|
1320
|
+
set_marking_from_a_hash( free_places( marking_array_for_free_places ) )
|
1293
1321
|
end
|
1294
1322
|
|
1295
|
-
# Set
|
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.
|
1336
|
+
# Duplicate creation.
|
1309
1337
|
#
|
1310
|
-
def
|
1311
|
-
|
1312
|
-
|
1313
|
-
|
1314
|
-
|
1315
|
-
|
1316
|
-
|
1317
|
-
|
1318
|
-
|
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
|
-
#
|
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
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
@step_size =
|
87
|
-
@sampling_period =
|
88
|
-
@target_time =
|
89
|
-
@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
|
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
|
101
|
-
oo
|
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
|
-
|
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
|
-
|
170
|
-
|
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
|
|