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