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