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 +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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ef666cef4bef63ff531f923b5b5c15dbf44f3d01
|
4
|
+
data.tar.gz: 39b9e0478d5fbe58ea0f351d930c307acdeab419
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
#
|
56
|
-
#
|
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
|
data/lib/y_petri/core/timed.rb
CHANGED
@@ -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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
#
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
#
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
#
|
53
|
-
#
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
5
|
-
# +#simulation+ method returning the +Simulation+ instance with which
|
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 :
|
11
|
-
:
|
10
|
+
delegate :PlacePS,
|
11
|
+
:TransitionPS,
|
12
12
|
:MarkingClamp,
|
13
13
|
:InitialMarkingObject,
|
14
|
-
:
|
15
|
-
:
|
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
|
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 )[
|
34
|
+
annotated_with( places )[ arg ]
|
29
35
|
end
|
30
36
|
else
|
31
|
-
self[
|
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
|
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
|
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
|
-
|
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
|
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;
|
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
|
-
|
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 )
|
93
|
-
# as protected in real Simulation instances, while
|
94
|
-
# places method just above does.
|
92
|
+
def Places( array )
|
95
93
|
# Kernel.p array
|
96
|
-
|
94
|
+
PlacesPS().load array.map &method( :place )
|
97
95
|
end
|
98
96
|
|
99
|
-
#
|
100
|
-
#
|
101
|
-
# arguments,
|
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
|
11
|
-
|
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
|
19
|
-
|
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 ?
|
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.
|