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.
@@ -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
- # just_before: last step has normal size, simulation stops before or just
148
- # on the target time
149
- # just_after: last step has normal size, simulation stops after or just
150
- # on the target time_step
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 :before then
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 :after then
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
- @core = if @guarded then
263
- YPetri::Core::Timed.new( simulation: self, method: method, guarded: true )
264
- else
265
- YPetri::Core::Timed.new( simulation: self, method: method, guarded: false )
266
- end
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: :S_Transitions,
41
- s_tt: :S_transitions,
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; Transition().instance( transition ); rescue NameError, TypeError
69
+ begin; TransitionPS().instance( transition ); rescue NameError, TypeError
70
70
  begin
71
71
  transition = net.transition( transition )
72
- Transition().instances.find { |t_rep| t_rep.source == transition } ||
73
- Transition().instance( transition.name )
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
- Transitions().load array.map &method( :transition )
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( self.class ).tap do |klass|
22
- klass.class_exec { include Type_s }
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( self.class ).tap do |klass|
30
- klass.class_exec { include Type_S }
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( self.class ).tap do |klass|
38
- klass.class_exec { include Type_t }
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( self.class ).tap do |klass|
46
- klass.class_exec { include Type_T }
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( self.class ).tap do |klass|
54
- klass.class_exec { include Type_ts }
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( self.class ).tap do |klass|
62
- klass.class_exec { include Type_tS }
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( self.class ).tap do |klass|
70
- klass.class_exec { include Type_Ts }
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( self.class ).tap do |klass|
78
- klass.class_exec { include Type_TS }
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( self.class ).tap do |klass|
86
- klass.class_exec { include Type_A }
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( self.class ).tap do |klass|
94
- klass.class_exec { include Type_a }
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 ? Transition().new( t, name: t.name ) : Transition().new( t )
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
@@ -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!( { Place: PlaceRepresentation, # parametrized subclasses
140
- Places: Places,
141
- Transition: TransitionRepresentation,
142
- Transitions: Transitions,
143
- Nodes: Nodes,
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
- [ Place(), Transition() ].each &:namespace! # each serves as its namespace
148
+ [ PlacePS(), TransitionPS() ].each &:namespace! # each serves as its namespace
150
149
  @guarded = guarded # TODO: Not operable as of now.
151
- @places = Places().load( net.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 = Transitions().load( net.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
@@ -1,4 +1,4 @@
1
1
  module YPetri
2
- VERSION = "2.3.12"
2
+ VERSION = "2.4.0"
3
3
  DEBUG = false
4
4
  end
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, but "world" is shorter. Its instance holds
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.