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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3fe2aaae8033f7806b851cd4293f555d15beb5a5
4
- data.tar.gz: 79271fa571273448d6716b547785f66805b5d5b4
3
+ metadata.gz: ef666cef4bef63ff531f923b5b5c15dbf44f3d01
4
+ data.tar.gz: 39b9e0478d5fbe58ea0f351d930c307acdeab419
5
5
  SHA512:
6
- metadata.gz: 14a6aba15f89a662ee2e13e16dc09695c1d00ccbe60ce7f969b9ae2a348f9bc8c98f51a2090643671b568d06f36419d162dda8ef5005fd34f014ec28aa75a855
7
- data.tar.gz: 66681f89b12fc840eabc2d225af8e137492870c61c8d844b8fcb73cd78ef712bfe46c497882be534a98e5287bb76e7aa97a726f032bd16346cb79a735d4069e9
6
+ metadata.gz: c8c52cbb6efd249b118a8e9347a478725d2916354c894a63b1ef6da8bc7e517f0af702a0479cc39b3ee334b0579d697ce7926fb2e2de03850c8707aaeebea457
7
+ data.tar.gz: 8a3ed7e54e271be653a563401abeeebb8409e5c34b76eeba6f8ccc49af59c9e86d3427917840ae0adbab28f4c4f93d92e318acb8776a313ff4f915f856081bad
@@ -3,57 +3,111 @@
3
3
  # Runge-Kutta method. Like vanilla Euler method, assumes that only T transitions are in the net.
4
4
  #
5
5
  module YPetri::Core::Timed::RungeKutta
6
+ # Computes delta by Runge-Kutta 4th order method.
7
+ #
6
8
  def delta Δt
7
9
  # The f below is from the equation state' = f( state )
8
- f = lambda { |mv| # mv is the marking vector of the free places
9
- result = "make hash from free places of the simulation to zeros"
10
- nonstoichiometric_transitions.each { |t|
11
- places = t.codomain.free
12
- inputs = mv.select( t.domain ) # this doesn't work this way
13
- f = t.function
14
- output = f.call( *inputs )
15
- places.each { |p|
16
- result[p] += output[p] # again, this doesn't work this way
17
- }
18
- }
19
- stoichiometric_transitions.each { |t|
20
- places = t.codomain.free
21
- inputs = mv.select( t.domain ) # this doesn't work this way
22
- f = t.function
23
- output = f.call( *inputs ) * stoichiometry_vector # this doesn't work this way
24
- places.each { |p|
25
- result[p] += output[p]
26
- }
27
- }
28
- # so you see how many selections one has to do if one doesn't
29
- # construct a specific gadget for the operation, that's why
30
- # I made those closures, unfortunately I didn't think about
31
- # higher-order methods yet when making them, and maybe the core
32
- # should own them instead of the simulation object
33
-
34
- return result.to_vector # this doesn't work this way
35
- }
36
-
37
- # this is supposed to be Runge-Kutta 4th order
38
- # but how do I get those in-between f values...
39
-
40
- y = simulation.state # this must return a vector compatible with the one
41
- # returned by f
42
-
43
- k1 = f( y )
44
- k2 = f( y + Δt / 2 * k1 )
45
- k3 = f( y + Δt / 2 * k2 )
46
- k4 = f( y + Δt * k3 )
47
-
48
- rslt = Δt / 6 * ( k1 + 2 * k2 + 2 * k3 + k4 )
49
- return rslt.to_the_kind_of_vector_that_delta_method_should_return
50
- # which is the vector corresponding to the ordered list of
51
- # free places of this simulation (here, every core either has a
52
- # simulation assigned, or is parametrized with simulation, which
53
- # might be dumb anyway, since core, by its name, should not be that
54
- # heavily coupled with a simulation, but actually, atm, each core
55
- # belongs to a specific simulation, so it's OK if it's parametrized
56
- # with it for now
10
+ f = lambda do |mv| # mv is the marking vector of the free places
11
+ # Delta from s transitions.
12
+ # TODO: This is only array now. Make it something else. One possibility
13
+ # would be to use simulation's MarkingVector class, but core should
14
+ # actually have its own marking vector class, probably parametrized by
15
+ # the net. It does not matter because alone I won't be able to exhaust
16
+ # all the possibilities.
17
+ delta_s = simulation.MarkingVector.zero( simulation.free_pp )
18
+ # Here, we get the nonstoichiometric transitions of the simulation.
19
+ nonstoichio_tt = simulation.s_tt
20
+ # Now, let's get the delta contribution of the nonstoichio. tt.
21
+ nonstoichio_tt.each { |t|
22
+ domain, codomain = t.domain, t.codomain # transition's domain
23
+ function = t.rate_closure # transition's function
24
+ output = Array function.call( *domain.map { |p| mv.fetch p } )
25
+ codomain.each_with_index do |p, i|
26
+ delta_s.set( p, delta_s.fetch( p ) + output[i] )
27
+ end
28
+ # TODO: The above code is suboptimal, needlessly computing
29
+ # MarkingVector#index and #fetch( place ) each time.
30
+ # The array incrementing might not be the best choice either,
31
+ # and most of all, the whole thing would need to be compiled
32
+ # into assembly language or at least FORTRAN.
33
+ }
34
+
35
+ # Delta from S transitions.
36
+ # TODO: (Same remark as for s transitions, see above.)
37
+ delta_S = simulation.MarkingVector.zero( simulation.free_pp )
38
+ # Here, we get the stoichiometric transitions of the simulation
39
+ stoichio_tt = simulation.S_tt
40
+ # Now, let's get the delta contribution of the stoichio. tt.
41
+ stoichio_tt.each { |t|
42
+ domain, codomain = t.domain, t.codomain # transition's domain
43
+ function = t.rate_closure # transition's function
44
+ s = t.stoichiometry
45
+ flux = function.call( *domain.map { |place| mv.fetch place } )
46
+ codomain.each_with_index do |p, i|
47
+ delta_S.set( p, delta_S.fetch( p ) + flux * s[i] )
48
+ end
49
+ # TODO: Again, the above code is suboptimal.
50
+ }
51
+
52
+ return delta_s + delta_S
53
+ end
54
+
55
+ y = marking_of_free_places
56
+
57
+ k1 = f.( y ) # puts "k1 ( = f( y ) ) is #{k1}"
58
+ k2 = f.( y + Δt / 2 * k1 ) # puts "k2 is #{k2}"
59
+ k3 = f.( y + Δt / 2 * k2 ) # puts "k3 is #{k3}"
60
+ k4 = f.( y + Δt * k3 ) # puts "k4 is #{k4}"
61
+
62
+ rslt = Δt / 6 * ( k1 + 2 * k2 + 2 * k3 + k4 ) # puts "rslt is #{rslt}"
63
+
64
+ return rslt # Marking vector of free places
57
65
  end
58
66
  alias Δ delta
67
+
68
+ def step! Δt=simulation.step
69
+ # TODO: Thus far, runge_kutta method is an exception in the core in
70
+ # that it works with core's own state. (Core used to work with
71
+ # simulation's state before and rely on the simulation to provide
72
+ # state increment and assign closures.) This is how whole core should
73
+ # work.
74
+ increment_marking_of_free_places Δ( Δt )
75
+ increment_time! Δt
76
+ alert_user! marking_of_free_places
77
+ end
78
+
79
+ def increment_marking_of_free_places by
80
+ # TODO: Same remark as above.
81
+ @marking_of_free_places += by
82
+ end
83
+
84
+ def increment_time! by
85
+ # TODO: Once other timed methods than runge_kutta are reasonable, this
86
+ # should be moved to core/timed.rb
87
+ @time += by
88
+ end
89
+
90
+ def reset_time! to=0.0
91
+ # TODO: Once other timed methods than runge_kutta are reasonable, this
92
+ # should be moved to core/timed.rb
93
+ @time = to
94
+ end
95
+
96
+ def set_user_alert_closure &block
97
+ # TODO: Core's runge_kutta method is special for now, and even
98
+ # simulation recognizes that. With runge_kutta method, core uses
99
+ # single @user_alert_closure which it calls whenever the state
100
+ # of the core progresses. It is the business of the user to supply,
101
+ # before using the core, that does what the user wants. It is also
102
+ # imaginable that different core's modes of operation would have
103
+ # different sensitivity with regard to alerting the user, but for now,
104
+ # the user is alerted whenever anything happens at all.
105
+ @user_alert_closure = block
106
+ end
107
+
108
+ def alert_user! object
109
+ # TODO: As soon as more core's method begin relying on core's own state,
110
+ # this method will be moved to Core module.
111
+ @user_alert_closure.call( object )
112
+ end
59
113
  end # YPetri::Core::Timed::RungeKutta
@@ -5,9 +5,9 @@
5
5
  #
6
6
  class YPetri::Core::Timed
7
7
  ★ YPetri::Core
8
-
8
+
9
9
  require_relative 'timed/basic'
10
- require_relative 'timed/ticked' #
10
+ require_relative 'timed/ticked'
11
11
  require_relative 'timed/euler'
12
12
  require_relative 'timed/runge_kutta'
13
13
  require_relative 'timed/gillespie'
@@ -20,20 +20,38 @@ class YPetri::Core::Timed
20
20
  gillespie: Gillespie # for timed nets only
21
21
  }
22
22
 
23
+ # From now on, core has its own time attribute and selector.
24
+ attr_reader :time
25
+
26
+ # This inquirer (=Boolean selector) is always true for timed cores.
27
+ #
28
+ def timed?; true end
29
+
30
+ # This inquirer (=Boolean selector) is always false for timed cores.
31
+ #
32
+ def timeless?; false end
33
+
23
34
  def initialize **named_args
24
- super
35
+ super # TODO: Net type checking.
36
+ @time = 0.0
25
37
  extend METHODS.fetch simulation_method
26
- # look in the Core#initialize method for the closures and parameters
27
- # and such. Here, we could define:
28
- @Ts_gradient_closure = simulation.Ts_gradient_closure
29
- # which would remind us that this machine needs to be
30
- # actually defined internal to Core instance
38
+ @delta_s = simulation.MarkingVector.zero( @free_pp )
39
+ @delta_S = simulation.MarkingVector.zero( @free_pp )
40
+ end
41
+
42
+ # Increments core time by Δt.
43
+ #
44
+ def increment_time! Δt
45
+ @time += Δt
31
46
  end
32
47
 
33
48
  # Makes a single step by Δt.
34
49
  #
35
50
  def step! Δt=simulation.step
51
+ # TODO: This one will act directly upon simulation. Subject of potential
52
+ # change later.
36
53
  increment_marking_vector Δ( Δt )
54
+ # TODO: The bottom two, obviously, act directly upon simulation.
37
55
  simulation.increment_time! Δt
38
56
  simulation.recorder.alert
39
57
  end
@@ -12,6 +12,14 @@ class YPetri::Core::Timeless
12
12
  # Note: the reason why Timeless core has distinct basic method is because
13
13
  # without having to consider timed transitions, it can be made simpler.
14
14
 
15
+ # This inquirer (=Boolean selector) is always false for timeless cores.
16
+ #
17
+ def timed?; false end
18
+
19
+ # This inquirer (=Boolean selector) is always true for timeless cores.
20
+ #
21
+ def timeless?; true end
22
+
15
23
  def initialize **named_args
16
24
  super
17
25
  extend METHODS.fetch simulation_method
data/lib/y_petri/core.rb CHANGED
@@ -11,58 +11,69 @@ module YPetri::Core
11
11
 
12
12
  DEFAULT_METHOD = :basic
13
13
 
14
- # I'm doing it this way in order to gradually begin decoupling in my mind
15
- # core from simulation. The constructed core will have to be assigned the
16
- # simulation object on which it will depend before core is made completely
17
- # independend on simulation. (Not gonna happen any soon.)
18
- #
14
+ # Instead of getting the parametrized subclass of Timed/Timeless core
15
+ # belonging to a simulation, I am passing a simulation instance to the
16
+ # constructor now in order to gradually begin decoupling in my mind core
17
+ # simulation. If I ever make the core independent from the simulation,
18
+ # it will have its own representation of the net and its own capability
19
+ # to form wiring and apply the required numerical procedures.
20
+
19
21
  attr_reader :simulation # just a remark:
20
22
  attr_reader :simulation_method # "reader" is "selector" in Landin's language
23
+ attr_reader :guarded
24
+ alias guarded? guarded
25
+
26
+ attr_reader :free_pp,
27
+ :clamped_pp,
28
+ :pp,
29
+ :marking_of_free_places,
30
+ :marking_of_clamped_places
21
31
 
22
32
  def initialize simulation: nil, method: nil, guarded: false, **named_args
23
33
  @simulation = simulation or fail ArgumentError, "Core requires simulation!"
24
34
  @simulation_method = method || DEFAULT_METHOD
25
-
35
+
26
36
  if guarded then # TODO: Guarded is unfinished business.
27
37
  fail NotImplementedMethod, "Guarded core is not implemented yet!"
28
38
  require_relative 'core/guarded' # TODO: Should be replaced with autoload.
29
39
  else @guarded = false end
30
-
31
- # Dependent on Simulation, this machine returns "delta contribution for ts
32
- # (timeless nonstoichiometric) transitions", which smells like a vector of
33
- # size corresponding to the number of free places.
34
- #
35
- @delta_closure_for_ts_transitions = simulation.ts_delta_closure
36
-
37
- # This one is slightly different in that it returns so-called "firing vector",
38
- # from which delta vector is computed by multiplying it with tS stoichiometry
39
- # matrix.
40
- #
41
- @firing_closure_for_tS_transitions = simulation.tS_firing_closure
42
-
43
- # This machine is special in that it directly modifies the marking vector,
44
- # firing the assignment transitions one by one (or so I think).
45
- #
46
- @assignment_closure_for_A_transitions = simulation.A_direct_assignment_closure
47
-
48
- # We're gonna change all of the above. The marking vectors will be owned by
49
- # the core now. Machines will be wired only inside the core.
40
+
41
+ @free_pp = simulation.free_pp
42
+ @clamped_pp = simulation.clamped_pp
43
+ @pp = simulation.pp
44
+ # TODO: Try to make the lines below simpler. In particular, in the future, core should
45
+ # not depend on simulation at this level.
46
+ @marking_of_free_places = simulation.MarkingVector.starting( @free_pp )
47
+ @marking_of_clamped_places = simulation.MarkingVector.starting( @clamped_pp )
48
+
49
+ # TODO: I don't remember how to load this in a simple way.
50
+ simulation.state.to_hash.each do |place, value|
51
+ if @marking_of_free_places.annotation.include? place then
52
+ @marking_of_free_places.set( place, value )
53
+ elsif @marking_of_clamped_places.annotation.include? place then
54
+ @marking_of_clamped_places.set( place, value )
55
+ else
56
+ fail "Problem loading marking vector to timed core."
57
+ end
58
+ end
50
59
  end
51
60
 
52
- # TODO: this delegation below is not completely right.
53
- # 1. There is no subclassing and Timed/Timeless module inclusion, so there
54
- # is no need to delegate timed?/timeless? to the class, if only the modules
55
- # provide those inquirer methods (predicates in Landin's language).
56
- # 2. Same goes for guarded? predicate, which might be the business of
57
- # Core::Guarded module (or not)
58
- #
59
- delegate :timed?,
60
- :timeless?,
61
- :guarded?,
62
- to: "self.class"
61
+ # Selector of the core's own state vector.
62
+ #
63
+ def state
64
+ # TODO: Make it more efficient. Later, when core is detached from
65
+ # simulation, use own assets instead of simulation.MarkingVector
66
+ simulation.MarkingVector.zero.tap do |mv|
67
+ @marking_of_free_places.annotation.each do |place|
68
+ mv.set place, @marking_of_free_places.fetch( place )
69
+ end
70
+ @marking_of_clamped_places.annotation.each do |place|
71
+ mv.set place, @marking_of_clamped_places.fetch( place )
72
+ end
73
+ end
74
+ end
63
75
 
64
- delegate :alert!,
65
- to: :recorder
76
+ delegate :alert!, to: :recorder
66
77
 
67
78
  # Delta for free places from timeless transitions.
68
79
  #
@@ -148,4 +159,3 @@ end # module YPetri::Core
148
159
  # deterministic (continous) and stochastic (discrete, going quantum by
149
160
  # quantum, or by several quanta, there is more than one stochastic discrete
150
161
  # method in stock) methods for simulation of individual processes.
151
-
@@ -1,18 +1,18 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  # Mixin providing collections of places / transitions to classes parametrized
4
- # by an instance of YPetri::Simulation. Expects the includer classes to provide
5
- # +#simulation+ method returning the +Simulation+ instance with which they are
6
- # parametrized.
4
+ # by an instance of +YPetri::Simulation+. Expects the includer classes to
5
+ # provide +#simulation+ method returning the +Simulation+ instance with which
6
+ # they are parametrized.
7
7
  #
8
8
  class YPetri::Simulation
9
9
  module Dependency
10
- delegate :Place,
11
- :Transition,
10
+ delegate :PlacePS,
11
+ :TransitionPS,
12
12
  :MarkingClamp,
13
13
  :InitialMarkingObject,
14
- :Places,
15
- :Transitions,
14
+ :PlacesPS,
15
+ :TransitionsPS,
16
16
  :MarkingClamps,
17
17
  :InitialMarking,
18
18
  :net,
@@ -37,7 +37,7 @@ class YPetri::Simulation
37
37
  end
38
38
  end
39
39
  end
40
-
40
+
41
41
  # Necessary to overcome the protected character of the listed methods.
42
42
  #
43
43
  [ :node,
@@ -1,7 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  class YPetri::Simulation
4
- # The class in which places' marking is stored insid a simulation.
4
+ # The class in which places' marking is stored inside a simulation.
5
5
  #
6
6
  class MarkingVector < Matrix
7
7
  ★ Dependency
@@ -14,6 +14,12 @@ class YPetri::Simulation
14
14
  # Constructs a marking vector from a hash places >> values, or from
15
15
  # an array, in which case, it is assumed that the marking vector
16
16
  # corresponds to all the places in the simulation.
17
+ #
18
+ # TODO: I don't like having to write MarkingVector[ [ 1, 2, 3 ] ] instead
19
+ # of MarkingVector[ 1, 2, 3 ], but I accepted and endorsed it long time
20
+ # ago as a necessary tax for being able to distinguish between the user
21
+ # meaning to supply no arguments and the user meaning to supply empty
22
+ # vector.
17
23
  #
18
24
  def [] arg
19
25
  case arg
@@ -25,10 +31,10 @@ class YPetri::Simulation
25
31
  msg unless arg.size == annotation.size
26
32
  column_vector( arg )
27
33
  else
28
- annotated_with( places )[ args ]
34
+ annotated_with( places )[ arg ]
29
35
  end
30
36
  else
31
- self[ args.each.to_a ]
37
+ self[ arg.each.to_a ]
32
38
  end
33
39
  end
34
40
 
@@ -46,7 +52,7 @@ class YPetri::Simulation
46
52
  #
47
53
  def starting places=nil
48
54
  if places.nil? then
49
- return starting places() if annotation.nil?
55
+ return starting( places() ) if annotation.nil?
50
56
  self[ annotation.map { |p| p.free? ? p.initial_marking : p.clamp } ]
51
57
  else
52
58
  annotated_with( places ).starting
@@ -80,7 +86,7 @@ class YPetri::Simulation
80
86
  end
81
87
  end
82
88
 
83
- # Modifying the vector nodes.
89
+ # Modifying the vector elements.
84
90
  #
85
91
  def set id, value
86
92
  self[ index( id ), 0 ] = value
@@ -92,17 +98,25 @@ class YPetri::Simulation
92
98
  def reset! arg=self.class.starting
93
99
  case arg
94
100
  when Hash then
101
+ # Hash is first converted into a PlaceMapping instance (mp).
95
102
  mp = simulation.PlaceMapping().load( arg )
103
+ # Updated marking vector is constructed using reliable methods
104
+ # self.class.starting and self#set.
96
105
  updated = mp.each_with_object self.class.starting do |(place, value), mv|
97
106
  mv.set place, value
98
107
  end
108
+ # Updated marking vector is then converted into an array and #reset! method
109
+ # is called upon it again to actually perform in-place update of this vector.
110
+ # TODO: The above is slightly inefficient -- constructing a new vector when
111
+ # in-place modification seems a better solution. But if it works, it's a
112
+ # strong reason to not fix it until we are in the optimization stage.
99
113
  reset! updated.column_to_a
100
114
  else # array arg assumed
101
115
  arg.each.to_a.zip( annotation ).map { |value, place| set place, value }
102
116
  end
103
117
  end
104
118
 
105
- # Access of the vector nodes.
119
+ # Access of the vector elements.
106
120
  #
107
121
  def fetch id
108
122
  self[ index( id ), 0 ]
@@ -39,7 +39,7 @@ class Matrix
39
39
  end
40
40
  code_lines.compact.join( "\n" ) << "\n"
41
41
  end
42
-
42
+
43
43
  # Builds a code string for incrementing a vector at given indices. Source is
44
44
  # an array.
45
45
  #
@@ -63,4 +63,14 @@ class Matrix
63
63
  indices: indices,
64
64
  source: "delta" )
65
65
  end
66
+
67
+ # Builds a closure for assigning to a column at given indices.
68
+ #
69
+ def assign_at_indices_closure indices: (fail ArgumentError, "No indices!")
70
+ v = self
71
+ eval "-> ass do\n%s\nend" %
72
+ self.class.column_vector_assignment_code( vector: ?v,
73
+ indices: indices,
74
+ source: "ass" )
75
+ end
66
76
  end
@@ -62,7 +62,7 @@ class YPetri::Simulation::Nodes
62
62
  # NOTE: At the moment, the Simulation instance does not have a
63
63
  # parametrized subclass of Simulation::Nodes class, the following
64
64
  # statement is thus made to return a plain array of elements.
65
- Nodes().load array.map &method( :node )
65
+ NodesPS().load array.map &method( :node )
66
66
  end
67
67
 
68
68
  # Without arguments, returns all the nodes (places / transitions) of the
@@ -5,7 +5,7 @@ class YPetri::Simulation
5
5
  #
6
6
  class Nodes < Array
7
7
  ★ Dependency
8
-
8
+
9
9
  class << self
10
10
  # New collection constructor
11
11
  #
@@ -13,15 +13,15 @@ class YPetri::Simulation
13
13
  new.tap { |inst| inst.load collection }
14
14
  end
15
15
  end
16
-
16
+
17
17
  delegate :simulation, to: "self.class"
18
-
18
+
19
19
  # Loads nodes to this collection.
20
20
  #
21
21
  def load nodes
22
22
  nodes.each{ |node| push node }
23
23
  end
24
-
24
+
25
25
  # Creates a subset of this collection (of the same class).
26
26
  #
27
27
  def subset nodes=nil, &block # TODO: Rename to subarray
@@ -37,7 +37,7 @@ class YPetri::Simulation
37
37
  self.class.load( nn )
38
38
  end
39
39
  end
40
-
40
+
41
41
  # Returns an array of the node sources (nodes in the underlying net).
42
42
  #
43
43
  def sources
@@ -26,7 +26,7 @@ class YPetri::Simulation
26
26
  # Returns the initial marking as a column vector.
27
27
  #
28
28
  def vector
29
- simulation.MarkingVector[ self ]
29
+ simulation.MarkingVector[ *self ]
30
30
  end
31
31
  alias to_marking_vector vector
32
32
 
@@ -60,5 +60,5 @@ class YPetri::Simulation
60
60
  def keys_to_source_places
61
61
  with_keys do |key| key.source end
62
62
  end
63
- end # class InitialMarking
63
+ end # class PlaceMapping
64
64
  end # class YPetri::Simulation
@@ -66,11 +66,11 @@ class YPetri::Simulation::Places
66
66
  # Place instance identification.
67
67
  #
68
68
  def place( place )
69
- begin; Place().instance( place ); rescue NameError, TypeError
69
+ begin; PlacePS().instance( place ); rescue NameError, TypeError
70
70
  begin
71
71
  place = net.place( place )
72
72
  places.find { |place_rep| place_rep.source == place } ||
73
- Place().instance( place.name )
73
+ PlacePS().instance( place.name )
74
74
  rescue NameError, TypeError => msg
75
75
  raise # FIXME: This raise needs to be here in order for the current
76
76
  # tests to pass (they expect NameError, while the raise below would
@@ -89,16 +89,17 @@ class YPetri::Simulation::Places
89
89
  # that even without an argument, it does not fail, but returns the @Places
90
90
  # parametrized subclass itself.
91
91
  #
92
- def Places( array ) # TODO: Understand why this method does not behave
93
- # as protected in real Simulation instances, while
94
- # places method just above does.
92
+ def Places( array )
95
93
  # Kernel.p array
96
- Places().load array.map &method( :place )
94
+ PlacesPS().load array.map &method( :place )
97
95
  end
98
96
 
99
- # Without arguments, returns all the place representations in the simulation.
100
- # Otherwise, it accepts an arbitrary number of places or place ids as
101
- # arguments, and a corresponding array of place representations.
97
+ # This method is overloaded in such way, that when called without arguments,
98
+ # it acts as a selector of @places property (instance variable) of the
99
+ # simulation. When called with arguments, all the arguments must be places
100
+ # or symbols (or strings etc.) identifying a place, and for these arguments,
101
+ # the method returns an array of corresponding places (more precisely,
102
+ # objects that represent places in the context of the receiver simulation).
102
103
  #
103
104
  def places( *places )
104
105
  return @places if places.empty?
@@ -1,5 +1,8 @@
1
1
  # encoding: utf-8
2
2
 
3
+ require_relative 'free'
4
+ require_relative 'clamped'
5
+
3
6
  # A mixin with place type selectors.
4
7
  #
5
8
  class YPetri::Simulation::Places
@@ -7,16 +10,16 @@ class YPetri::Simulation::Places
7
10
  # Subset of free places, if any.
8
11
  #
9
12
  def free
10
- ( @Type_free ||= Class.new( self.class ).tap do |klass|
11
- klass.class_exec { include Type_free }
13
+ ( @Type_free ||= Class.new self.class do
14
+ include Type_free
12
15
  end ).load subset( &:free? )
13
16
  end
14
-
17
+
15
18
  # Subset of clamped places, if any.
16
19
  #
17
20
  def clamped
18
- ( @Type_clamped ||= Class.new( self.class ).tap do |klass|
19
- klass.class_exec { include Type_clamped }
21
+ ( @Type_clamped ||= Class.new self.class do
22
+ include Type_clamped
20
23
  end ).load subset( &:clamped? )
21
24
  end
22
25
  end # Types
@@ -4,8 +4,6 @@
4
4
  #
5
5
  class YPetri::Simulation::Places < YPetri::Simulation::Nodes
6
6
  require_relative 'places/types'
7
- require_relative 'places/free'
8
- require_relative 'places/clamped'
9
7
 
10
8
  ★ Types
11
9
 
@@ -15,7 +13,7 @@ class YPetri::Simulation::Places < YPetri::Simulation::Nodes
15
13
  p = begin; net.place( place ); rescue NameError, TypeError
16
14
  return super place( place )
17
15
  end
18
- super p.name ? Place().new( p, name: p.name ) : Place().new( p )
16
+ super p.name ? PlacePS().new( p, name: p.name ) : PlacePS().new( p )
19
17
  end
20
18
 
21
19
  # Marking of the place collection in the current simulation.