y_petri 2.3.12 → 2.4.0
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/core/timed/runge_kutta.rb +103 -49
- data/lib/y_petri/core/timed.rb +26 -8
- data/lib/y_petri/core/timeless.rb +8 -0
- data/lib/y_petri/core.rb +50 -40
- data/lib/y_petri/simulation/dependency.rb +8 -8
- data/lib/y_petri/simulation/marking_vector.rb +20 -6
- data/lib/y_petri/simulation/matrix.rb +11 -1
- data/lib/y_petri/simulation/nodes/access.rb +1 -1
- data/lib/y_petri/simulation/nodes.rb +5 -5
- data/lib/y_petri/simulation/place_mapping.rb +2 -2
- data/lib/y_petri/simulation/places/access.rb +10 -9
- data/lib/y_petri/simulation/places/types.rb +8 -5
- data/lib/y_petri/simulation/places.rb +1 -3
- data/lib/y_petri/simulation/recorder.rb +23 -0
- data/lib/y_petri/simulation/timed.rb +104 -11
- data/lib/y_petri/simulation/transition_representation/types.rb +4 -12
- data/lib/y_petri/simulation/transitions/access.rb +6 -6
- data/lib/y_petri/simulation/transitions/types.rb +20 -30
- data/lib/y_petri/simulation/transitions.rb +3 -1
- data/lib/y_petri/simulation.rb +17 -14
- data/lib/y_petri/version.rb +1 -1
- data/lib/y_petri/world.rb +1 -1
- data/test/simulation_test.rb +211 -50
- metadata +2 -2
@@ -66,6 +66,29 @@ class YPetri::Simulation::Recorder
|
|
66
66
|
# Records the current state as a pair { sampling_event => system_state }.
|
67
67
|
#
|
68
68
|
def sample! event
|
69
|
+
# TODO: Method #get_features( features ) seems to be just a little bit
|
70
|
+
# roundabout way of doing things, but not much more so than #send method,
|
71
|
+
# so it's still somehow OK. The question is, who should know what the
|
72
|
+
# features are and how they are extracted from various objects. Features?
|
73
|
+
# Or those objects?
|
74
|
+
#
|
75
|
+
# Ruby already has one mechanism of features, which are called properties
|
76
|
+
# of objects, and are presented by selector methods. Features reified as
|
77
|
+
# first-class objects are themselves responsible for their functional
|
78
|
+
# definitions. Features as first-class objects are related to Ted Nelson's
|
79
|
+
# ZZ dimensions. Current "Features" will have to be somehow merged (or
|
80
|
+
# at least, it will be necessary to consider their merger) with the ZZ
|
81
|
+
# structures once YNelson is finished to the point to which it should be
|
82
|
+
# finished.
|
83
|
+
#
|
84
|
+
# It seems to me that there is no use to decide in advance and in general
|
85
|
+
# who should hold the functional definition of every feature and every
|
86
|
+
# object. Sometimes it is convenient if the objects help to compute their
|
87
|
+
# common features (akin to patients being able to tell the doctor their
|
88
|
+
# name and family history), while in other cases, the features themselves
|
89
|
+
# should define the procedures needed to extract the values from the
|
90
|
+
# objects (akin to the doctor examining the patient).
|
91
|
+
#
|
69
92
|
record = simulation.get_features( features )
|
70
93
|
recording[ event ] = record.dump( precision: SAMPLING_DECIMAL_PLACES )
|
71
94
|
end
|
@@ -144,22 +144,22 @@ module YPetri::Simulation::Timed
|
|
144
144
|
# options :just_before, :just_after and :exact, and tunes the simulation
|
145
145
|
# behavior towards the end of the run.
|
146
146
|
#
|
147
|
-
#
|
148
|
-
#
|
149
|
-
#
|
150
|
-
#
|
147
|
+
# stop_before: last step has normal size, simulation stops before or just
|
148
|
+
# on the target time
|
149
|
+
# stop_after: last step has normal size, simulation stops after or just
|
150
|
+
# on the target time_step
|
151
151
|
# exact: simulation stops exactly on the prescribed time, last step
|
152
152
|
# is shortened if necessary
|
153
153
|
#
|
154
154
|
def run_upto( target_time, final_step: :exact )
|
155
155
|
case final_step
|
156
|
-
when :
|
156
|
+
when :stop_before then
|
157
157
|
step! while time + step <= target_time
|
158
158
|
when :exact then
|
159
159
|
step! while time + step < target_time
|
160
160
|
step!( target_time - time )
|
161
161
|
@time = target_time
|
162
|
-
when :
|
162
|
+
when :stop_after then
|
163
163
|
step! while time < target_time
|
164
164
|
else
|
165
165
|
fail ArgumentError, "Unrecognized :final_step option: #{final_step}"
|
@@ -242,6 +242,24 @@ module YPetri::Simulation::Timed
|
|
242
242
|
@initial_time, @target_time = time_range.begin, time_range.end
|
243
243
|
@time_unit = initial_time.class.one
|
244
244
|
else
|
245
|
+
# TODO: When using simulation after some time, I found this behavior
|
246
|
+
# surprising. I wanted to call simulation time: 100, expecting it
|
247
|
+
# to run until 100 (in the range 0..100). Instead, I see that it wants
|
248
|
+
# to run from 100 to infinity. While I understand how important it
|
249
|
+
# is to have a simple way to set the time of a newly constructed
|
250
|
+
# simulation to some value (for the purposes such as cloning of
|
251
|
+
# simulation, interpolation of simulations etc. -- actually, there
|
252
|
+
# is no really stateful net in YPetri at the moment, so Simulation
|
253
|
+
# class behaves somewhat as a stateful net...), not just me, but
|
254
|
+
# other users might expect :time argument to set final time with
|
255
|
+
# initial time being 0. I'm not gonna change it quite yet.
|
256
|
+
#
|
257
|
+
# The way to refactor it would be to first introduce "initial_time"
|
258
|
+
# parameter and make "time" parameter raise an error, and refactor
|
259
|
+
# the code until the tests pass. Then, to reintroduce "time"
|
260
|
+
# parameter with the new, more intuitive meaning. Interactive
|
261
|
+
# users can always modify time later (simulation.time = something).
|
262
|
+
#
|
245
263
|
@initial_time = settings[:time]
|
246
264
|
@time_unit = initial_time.class.one
|
247
265
|
@target_time = time_unit * Float::INFINITY
|
@@ -259,11 +277,86 @@ module YPetri::Simulation::Timed
|
|
259
277
|
reset_time!
|
260
278
|
@step = settings[:step] || time_unit
|
261
279
|
@default_sampling = settings[:sampling] || step
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
280
|
+
if method == :runge_kutta then
|
281
|
+
# This is a bit irregular for now, but since the core has to behave
|
282
|
+
# differently (that is, more like a real simulation core), at least
|
283
|
+
# for the more advanced runge_kutta method, a core under a different
|
284
|
+
# instance variable will be constructed.
|
285
|
+
@rk_core = if @guarded then
|
286
|
+
YPetri::Core::Timed.new( simulation: self, method: method, guarded: true )
|
287
|
+
else
|
288
|
+
YPetri::Core::Timed.new( simulation: self, method: method, guarded: false )
|
289
|
+
end
|
290
|
+
singleton_class.class_exec do
|
291
|
+
attr_reader :rk_core
|
292
|
+
delegate :simulation_method,
|
293
|
+
:firing_vector_tS,
|
294
|
+
to: :rk_core
|
295
|
+
|
296
|
+
# This method steps the simulation forward by the prescribed step. Simulation uses the core to perform the #step! method.
|
297
|
+
#
|
298
|
+
def step! Δt=step()
|
299
|
+
# Pseudocode would be like this:
|
300
|
+
|
301
|
+
# 1. set_state_and_time_of_core_to_the_current_simulation's_state_and_time
|
302
|
+
|
303
|
+
# 2. explicitly tell the core the code by which to alert the sampler when necessary
|
304
|
+
# ie. when the state vector of the core progresses sufficiently for it to be
|
305
|
+
# interesting to the sampler
|
306
|
+
|
307
|
+
# 3. explicitly tell the core the code by which to update the simulation's state,
|
308
|
+
# and when should it be updated.
|
309
|
+
#
|
310
|
+
# (Note: This can be done in several ways. For example, one possibility is to
|
311
|
+
# update the simulation only after the core is finished computing. Another
|
312
|
+
# possibility is to have some other criterion to update the simulation more
|
313
|
+
# often in the course of the core's work. Since this is some sort of sampling
|
314
|
+
# job again, there is an option of actually delegating it to the sampler,
|
315
|
+
# which would thus get closer to its role of the interface.)
|
316
|
+
#
|
317
|
+
# (Note 2: It is actually more clear what the role of the core should be rather
|
318
|
+
# than what the simulation's role should be. The core should receive the initial
|
319
|
+
# instructions, a relatively simple method of what to do, and it should be
|
320
|
+
# specialized in doing its job fast. Secondly, it should receive the method for
|
321
|
+
# alerting the superiors: simulation and/or sampler. Thirdly, it should be told
|
322
|
+
# when to stop.)
|
323
|
+
|
324
|
+
# This should set the state of the rk_core to the marking vector of free
|
325
|
+
# places (#marking_vector method).
|
326
|
+
rk_core.marking_of_free_places.reset!( marking_vector )
|
327
|
+
sim, rec = self, recorder
|
328
|
+
rk_core.set_user_alert_closure do |mv_free| # marking vect. of free places
|
329
|
+
# TODO: This can be done differently. For example, the simulation can hand
|
330
|
+
# the core the function which, when handed a hash, will update the marking
|
331
|
+
# vector with the hash. This is actually quite similar, but there is some
|
332
|
+
# ugliness in it...
|
333
|
+
sim.m_vector.reset! mv_free.to_hash
|
334
|
+
sim.increment_time! Δt
|
335
|
+
Kernel.print '.'
|
336
|
+
rec.alert!
|
337
|
+
end
|
338
|
+
|
339
|
+
rk_core.step! Δt
|
340
|
+
|
341
|
+
# TODO: In the above lines, setting rec = recorder and then calling rec!.alert in
|
342
|
+
# the block is a bit weird. It would be nicer to use recorder.alert!, but maybe
|
343
|
+
# wise Ruby closure mechanism does not allow it...
|
344
|
+
end # def step!
|
345
|
+
end
|
346
|
+
else
|
347
|
+
@core = if @guarded then
|
348
|
+
YPetri::Core::Timed.new( simulation: self, method: method, guarded: true )
|
349
|
+
else
|
350
|
+
YPetri::Core::Timed.new( simulation: self, method: method, guarded: false )
|
351
|
+
end
|
352
|
+
singleton_class.class_exec do
|
353
|
+
attr_reader :core
|
354
|
+
delegate :simulation_method,
|
355
|
+
:step!,
|
356
|
+
:firing_vector_tS,
|
357
|
+
to: :core
|
358
|
+
end
|
359
|
+
end
|
267
360
|
@recorder = if features_to_record then
|
268
361
|
# we'll have to figure out features
|
269
362
|
ff = case features_to_record
|
@@ -37,26 +37,18 @@ class YPetri::Simulation::TransitionRepresentation
|
|
37
37
|
|
38
38
|
# Is this a TS transition?
|
39
39
|
#
|
40
|
-
def TS
|
41
|
-
type == :TS
|
42
|
-
end
|
40
|
+
def TS?; type == :TS end
|
43
41
|
|
44
42
|
# Is this a Ts transition?
|
45
43
|
#
|
46
|
-
def Ts
|
47
|
-
type == :Ts
|
48
|
-
end
|
44
|
+
def Ts?; type == :Ts end
|
49
45
|
|
50
46
|
# Is this a tS transition?
|
51
47
|
#
|
52
|
-
def tS
|
53
|
-
type == :tS
|
54
|
-
end
|
48
|
+
def tS?; type == :tS end
|
55
49
|
|
56
50
|
# Is this a ts transition?
|
57
51
|
#
|
58
|
-
def ts
|
59
|
-
type == :ts
|
60
|
-
end
|
52
|
+
def ts?; type == :ts end
|
61
53
|
end # module Types
|
62
54
|
end # class YPetri::Simulation::TransitionRepresentation
|
@@ -37,8 +37,8 @@ class YPetri::Simulation::Transitions
|
|
37
37
|
A_tt: :A_transitions,
|
38
38
|
S_Tt: :S_Transitions,
|
39
39
|
S_tt: :S_transitions,
|
40
|
-
s_Tt: :
|
41
|
-
s_tt: :
|
40
|
+
s_Tt: :s_Transitions,
|
41
|
+
s_tt: :s_transitions,
|
42
42
|
T_Tt: :T_Transitions,
|
43
43
|
T_tt: :T_transitions,
|
44
44
|
t_Tt: :t_Transitions,
|
@@ -66,11 +66,11 @@ class YPetri::Simulation::Transitions
|
|
66
66
|
# Transition instance identification.
|
67
67
|
#
|
68
68
|
def transition( transition )
|
69
|
-
begin;
|
69
|
+
begin; TransitionPS().instance( transition ); rescue NameError, TypeError
|
70
70
|
begin
|
71
71
|
transition = net.transition( transition )
|
72
|
-
|
73
|
-
|
72
|
+
TransitionPS().instances.find { |t_rep| t_rep.source == transition } ||
|
73
|
+
TransitionPS().instance( transition.name )
|
74
74
|
rescue NameError, TypeError => msg
|
75
75
|
fail TypeError, "Unknown transition instance: #{transition}! (#{msg})"
|
76
76
|
end
|
@@ -85,7 +85,7 @@ class YPetri::Simulation::Transitions
|
|
85
85
|
# not fil, but returns @Transitions parametrized subclass itself.
|
86
86
|
#
|
87
87
|
def Transitions( array )
|
88
|
-
|
88
|
+
TransitionsPS().load array.map &method( :transition )
|
89
89
|
end
|
90
90
|
|
91
91
|
# Without arguments, returns all the transition representations in the
|
@@ -18,81 +18,71 @@ class YPetri::Simulation::Transitions
|
|
18
18
|
# Subset of s type transitions, if any.
|
19
19
|
#
|
20
20
|
def s
|
21
|
-
( @Type_s ||= Class.new
|
22
|
-
|
23
|
-
end ).load subset( &:s? )
|
21
|
+
( @Type_s ||= Class.new self.class do include Type_s end )
|
22
|
+
.load subset( &:s? )
|
24
23
|
end
|
25
24
|
|
26
25
|
# Subset of S type transitions, if any.
|
27
26
|
#
|
28
27
|
def S
|
29
|
-
( @Type_S ||= Class.new
|
30
|
-
|
31
|
-
end ).load subset( &:S? )
|
28
|
+
( @Type_S ||= Class.new self.class do include Type_S end )
|
29
|
+
.load subset( &:S? )
|
32
30
|
end
|
33
31
|
|
34
32
|
# Subset of t type transitions, if any.
|
35
33
|
#
|
36
34
|
def t
|
37
|
-
( @Type_t ||= Class.new
|
38
|
-
|
39
|
-
end ).load subset( &:t? )
|
35
|
+
( @Type_t ||= Class.new self.class do include Type_t end )
|
36
|
+
.load subset( &:t? )
|
40
37
|
end
|
41
38
|
|
42
39
|
# Subset of T type transitions, if any.
|
43
40
|
#
|
44
41
|
def T
|
45
|
-
( @Type_T ||= Class.new
|
46
|
-
|
47
|
-
end ).load subset( &:T? )
|
42
|
+
( @Type_T ||= Class.new self.class do include Type_T end )
|
43
|
+
.load subset( &:T? )
|
48
44
|
end
|
49
45
|
|
50
46
|
# Subset of ts type transitions, if any.
|
51
47
|
#
|
52
48
|
def ts
|
53
|
-
( @Type_ts ||= Class.new
|
54
|
-
|
55
|
-
end ).load subset( &:ts? )
|
49
|
+
( @Type_ts ||= Class.new self.class do include Type_ts end )
|
50
|
+
.load subset( &:ts? )
|
56
51
|
end
|
57
52
|
|
58
53
|
# Subset of tS type transitions, if any.
|
59
54
|
#
|
60
55
|
def tS
|
61
|
-
( @Type_tS ||= Class.new
|
62
|
-
|
63
|
-
end ).load subset( &:tS? )
|
56
|
+
( @Type_tS ||= Class.new self.class do include Type_tS end )
|
57
|
+
.load subset( &:tS? )
|
64
58
|
end
|
65
59
|
|
66
60
|
# Subset of Ts type transitions, if any.
|
67
61
|
#
|
68
62
|
def Ts
|
69
|
-
( @Type_Ts ||= Class.new
|
70
|
-
|
71
|
-
end ).load subset( &:Ts? )
|
63
|
+
( @Type_Ts ||= Class.new self.class do include Type_Ts end )
|
64
|
+
.load subset( &:Ts? )
|
72
65
|
end
|
73
66
|
|
74
67
|
# Subset of TS type transitions, if any.
|
75
68
|
#
|
76
69
|
def TS
|
77
|
-
( @Type_TS ||= Class.new
|
78
|
-
|
79
|
-
end ).load subset( &:TS? )
|
70
|
+
( @Type_TS ||= Class.new self.class do include Type_TS end )
|
71
|
+
.load subset( &:TS? )
|
80
72
|
end
|
81
73
|
|
82
74
|
# Subset of A type transitions, if any.
|
83
75
|
#
|
84
76
|
def A
|
85
|
-
( @Type_A ||= Class.new
|
86
|
-
|
87
|
-
end ).load subset( &:A? )
|
77
|
+
( @Type_A ||= Class.new self.class do include Type_A end )
|
78
|
+
.load subset( &:A? )
|
88
79
|
end
|
89
80
|
|
90
81
|
# Subset of a type transitions, if any.
|
91
82
|
#
|
92
83
|
def a
|
93
|
-
( @Type_a ||= Class.new
|
94
|
-
|
95
|
-
end ).load subset( &:a? )
|
84
|
+
( @Type_a ||= Class.new self.class do include Type_a end )
|
85
|
+
.load subset( &:a? )
|
96
86
|
end
|
97
87
|
end # Types
|
98
88
|
end # class YPetri::Simulation::Transitions
|
@@ -13,7 +13,9 @@ class YPetri::Simulation
|
|
13
13
|
t = begin; net.transition( transition ); rescue NameError, TypeError
|
14
14
|
return super transition( transition )
|
15
15
|
end
|
16
|
-
super t.name ?
|
16
|
+
super t.name ?
|
17
|
+
TransitionPS().new( t, name: t.name ) :
|
18
|
+
TransitionTS().new( t )
|
17
19
|
end
|
18
20
|
end # class Transitions
|
19
21
|
end # class YPetri::Simulation::Transitions
|
data/lib/y_petri/simulation.rb
CHANGED
@@ -53,8 +53,7 @@ class YPetri::Simulation
|
|
53
53
|
end
|
54
54
|
|
55
55
|
# Parametrized subclasses.
|
56
|
-
attr_reader :core,
|
57
|
-
:recorder,
|
56
|
+
attr_reader :recorder, # :core,
|
58
57
|
:guarded,
|
59
58
|
:tS_stoichiometry_matrix,
|
60
59
|
:TS_stoichiometry_matrix,
|
@@ -71,15 +70,15 @@ class YPetri::Simulation
|
|
71
70
|
|
72
71
|
delegate :net, to: "self.class"
|
73
72
|
|
73
|
+
delegate :recording,
|
74
|
+
:back!,
|
75
|
+
to: :recorder
|
76
|
+
|
74
77
|
delegate :simulation_method,
|
75
78
|
:step!,
|
76
79
|
:firing_vector_tS,
|
77
80
|
to: :core
|
78
81
|
|
79
|
-
delegate :recording,
|
80
|
-
:back!,
|
81
|
-
to: :recorder
|
82
|
-
|
83
82
|
alias r recording
|
84
83
|
|
85
84
|
delegate :plot,
|
@@ -136,19 +135,19 @@ class YPetri::Simulation
|
|
136
135
|
initial_marking: {},
|
137
136
|
marking: nil,
|
138
137
|
**settings
|
139
|
-
param_class!( {
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
138
|
+
param_class!( { PlacePS: PlaceRepresentation, # PS = parametrized subclass
|
139
|
+
PlacesPS: Places,
|
140
|
+
TransitionPS: TransitionRepresentation,
|
141
|
+
TransitionsPS: Transitions,
|
142
|
+
NodesPS: Nodes,
|
144
143
|
PlaceMapping: PlaceMapping,
|
145
144
|
InitialMarking: InitialMarking,
|
146
145
|
MarkingClamps: MarkingClamps,
|
147
146
|
MarkingVector: MarkingVector },
|
148
147
|
with: { simulation: self } )
|
149
|
-
[
|
148
|
+
[ PlacePS(), TransitionPS() ].each &:namespace! # each serves as its namespace
|
150
149
|
@guarded = guarded # TODO: Not operable as of now.
|
151
|
-
@places =
|
150
|
+
@places = PlacesPS().load( net.places )
|
152
151
|
@marking_clamps = MarkingClamps().load( marking_clamps )
|
153
152
|
@initial_marking = if marking then
|
154
153
|
m = PlaceMapping().load( marking )
|
@@ -170,7 +169,7 @@ class YPetri::Simulation
|
|
170
169
|
# Initialize the marking vector.
|
171
170
|
@m_vector = MarkingVector().zero
|
172
171
|
# Set up the collection of transitions.
|
173
|
-
@transitions =
|
172
|
+
@transitions = TransitionsPS().load( net.transitions )
|
174
173
|
# Set up stoichiometry matrices relative to free places.
|
175
174
|
@tS_stoichiometry_matrix = transitions.tS.stoichiometry_matrix
|
176
175
|
@TS_stoichiometry_matrix = transitions.TS.stoichiometry_matrix
|
@@ -250,6 +249,10 @@ class YPetri::Simulation
|
|
250
249
|
places.zip( ary ).each { |pl, proposed_m| pl.guard.( proposed_m ) }
|
251
250
|
end
|
252
251
|
|
252
|
+
# TODO: The method below does nothing except that it delegates extraction
|
253
|
+
# of a set of features to Features class. Features understood in this way
|
254
|
+
# are similar to ZZ dimensions.
|
255
|
+
|
253
256
|
# Extract a prescribed set of features.
|
254
257
|
#
|
255
258
|
def get_features *args
|
data/lib/y_petri/version.rb
CHANGED
data/lib/y_petri/world.rb
CHANGED
@@ -4,7 +4,7 @@ require_relative 'world/dependency'
|
|
4
4
|
require_relative 'world/petri_net_aspect'
|
5
5
|
require_relative 'world/simulation_aspect'
|
6
6
|
|
7
|
-
# Represents YPetri workspace
|
7
|
+
# Represents YPetri workspace ("World" is shorter). Its instance holds
|
8
8
|
# places, transitions, nets and other assets needed to perform the tasks
|
9
9
|
# of system specification and simulation (simulation settings, place clamps,
|
10
10
|
# initial markings etc.). Provides basic methods to do just what is necessary.
|