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.
@@ -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.