y_petri 2.1.3 → 2.1.6

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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/lib/y_petri/agent/petri_net_related.rb +25 -5
  3. data/lib/y_petri/agent/selection.rb +12 -10
  4. data/lib/y_petri/agent/simulation_related.rb +14 -58
  5. data/lib/y_petri/agent.rb +15 -17
  6. data/lib/y_petri/core/timed/euler.rb +13 -15
  7. data/lib/y_petri/core/timed/pseudo_euler.rb +22 -24
  8. data/lib/y_petri/core/timed/quasi_euler.rb +15 -17
  9. data/lib/y_petri/core/timed.rb +42 -44
  10. data/lib/y_petri/core/timeless/pseudo_euler.rb +12 -14
  11. data/lib/y_petri/core/timeless.rb +10 -7
  12. data/lib/y_petri/core.rb +3 -3
  13. data/lib/y_petri/dsl.rb +46 -46
  14. data/lib/y_petri/fixed_assets.rb +8 -0
  15. data/lib/y_petri/net/data_set.rb +238 -0
  16. data/lib/y_petri/net/own_state.rb +63 -0
  17. data/lib/y_petri/net/state/feature/delta.rb +98 -71
  18. data/lib/y_petri/net/state/feature/firing.rb +51 -54
  19. data/lib/y_petri/net/state/feature/flux.rb +51 -55
  20. data/lib/y_petri/net/state/feature/gradient.rb +55 -59
  21. data/lib/y_petri/net/state/feature/marking.rb +55 -59
  22. data/lib/y_petri/net/state/feature.rb +65 -67
  23. data/lib/y_petri/net/state/features/record.rb +150 -43
  24. data/lib/y_petri/net/state/features.rb +252 -96
  25. data/lib/y_petri/net/state.rb +114 -106
  26. data/lib/y_petri/net/visualization.rb +3 -2
  27. data/lib/y_petri/net.rb +29 -24
  28. data/lib/y_petri/place/arcs.rb +3 -3
  29. data/lib/y_petri/place/guard.rb +35 -117
  30. data/lib/y_petri/place/guarded.rb +86 -0
  31. data/lib/y_petri/place.rb +6 -3
  32. data/lib/y_petri/simulation/element_representation.rb +2 -2
  33. data/lib/y_petri/simulation/elements.rb +3 -1
  34. data/lib/y_petri/simulation/feature_set.rb +3 -1
  35. data/lib/y_petri/simulation/marking_vector.rb +3 -1
  36. data/lib/y_petri/simulation/place_mapping.rb +3 -1
  37. data/lib/y_petri/simulation/places.rb +1 -1
  38. data/lib/y_petri/simulation/recorder.rb +60 -54
  39. data/lib/y_petri/simulation/timed/recorder.rb +12 -1
  40. data/lib/y_petri/simulation/timed.rb +173 -172
  41. data/lib/y_petri/simulation/transitions/access.rb +97 -29
  42. data/lib/y_petri/simulation.rb +11 -9
  43. data/lib/y_petri/transition/{assignment.rb → A.rb} +2 -2
  44. data/lib/y_petri/transition/{timed.rb → T.rb} +2 -2
  45. data/lib/y_petri/transition/arcs.rb +3 -3
  46. data/lib/y_petri/transition/cocking.rb +3 -3
  47. data/lib/y_petri/transition/{init.rb → construction_convenience.rb} +6 -53
  48. data/lib/y_petri/transition/{ordinary_timeless.rb → t.rb} +2 -2
  49. data/lib/y_petri/transition/type.rb +103 -0
  50. data/lib/y_petri/transition/type_information.rb +103 -0
  51. data/lib/y_petri/transition/types.rb +107 -0
  52. data/lib/y_petri/transition/usable_without_world.rb +14 -0
  53. data/lib/y_petri/transition.rb +87 -101
  54. data/lib/y_petri/version.rb +1 -1
  55. data/lib/y_petri/world/dependency.rb +30 -28
  56. data/lib/y_petri/world.rb +10 -8
  57. data/test/acceptance/basic_usage_test.rb +3 -3
  58. data/test/acceptance/simulation_test.rb +3 -3
  59. data/test/acceptance/simulation_with_physical_units_test.rb +2 -2
  60. data/test/acceptance/token_game_test.rb +2 -2
  61. data/test/acceptance/visualization_test.rb +3 -3
  62. data/test/acceptance_tests.rb +2 -2
  63. data/test/agent_test.rb +1 -1
  64. data/test/net_test.rb +41 -17
  65. data/test/place_test.rb +1 -1
  66. data/test/simulation_test.rb +39 -39
  67. data/test/transition_test.rb +1 -1
  68. data/test/world_test.rb +1 -1
  69. data/test/y_petri_test.rb +1 -1
  70. metadata +13 -8
  71. data/lib/y_petri/net/state/features/dataset.rb +0 -135
  72. data/lib/y_petri/transition/construction.rb +0 -311
@@ -0,0 +1,238 @@
1
+ # encoding: utf-8
2
+
3
+ # Dataset is a collection of labeled state records.
4
+ #
5
+ class YPetri::Net::DataSet < Hash
6
+ class << self
7
+ alias __new__ new
8
+
9
+ def new type: nil
10
+ __new__ do |hsh, missing|
11
+ case missing
12
+ when Float then nil
13
+ else hsh[ missing.to_f ] end
14
+ end.tap { |inst|
15
+ inst.instance_variable_set :@type, type
16
+ }
17
+ end
18
+
19
+ private :__new__
20
+
21
+ delegate :net, to: :features
22
+ delegate :State, to: :net
23
+ delegate :Marking, :Firing, :Flux, :Gradient, :Delta,
24
+ to: "State()"
25
+ end
26
+
27
+ alias events keys
28
+ alias records values
29
+
30
+ delegate :features,
31
+ :net,
32
+ :State,
33
+ :Marking, :Firing, :Flux, :Gradient, :Delta,
34
+ to: "self.class"
35
+
36
+ attr_reader :type # more like event_type, idea not matured yet
37
+
38
+ # Type of the dataset.
39
+ #
40
+ def timed?
41
+ type == :timed
42
+ end
43
+
44
+ # Returns a Record instance corresponding to the given recorded event.
45
+ #
46
+ def record( event )
47
+ features.load( fetch event )
48
+ end
49
+
50
+ # Revives records from values.
51
+ #
52
+ def records
53
+ values.map { |value| features.Record.new( value ) }
54
+ end
55
+
56
+ # Recreates the simulation at a given event label.
57
+ #
58
+ def reconstruct event: (fail "No event given!"),
59
+ **settings # settings include marking clamps
60
+ rec = interpolate( event )
61
+ if timed? then
62
+ rec.reconstruct time: event, **settings
63
+ else
64
+ rec.reconstruct **settings
65
+ end
66
+ end
67
+
68
+ # Interpolates the recording an the given point (event). Return value is the
69
+ # Record class instance.
70
+ #
71
+ def interpolate( event )
72
+ # TODO: This whole interpolation thing is unfinished.
73
+ begin
74
+ record( event )
75
+ rescue KeyError => msg
76
+ timed? or raise TypeError, "Event #{event} does not have a record! (%s)" %
77
+ "simulation type: #{type.nil? ? 'nil' : type}"
78
+ f_time, floor = floor( event ) # timed datasets support floor, ceiling
79
+ c_time, ceiling = ceiling( time )
80
+ floor + ( ceiling - floor ) / ( c_time - f_time ) * ( time - f_time )
81
+ end
82
+ end
83
+
84
+ # Returns the data series for the specified features.
85
+ #
86
+ def series arg=nil
87
+ return records.transpose if arg.nil?
88
+ reduce_features( State().features( arg ) ).series
89
+ end
90
+
91
+ # Expects a hash of features (:marking (alias :state) of places, :firing
92
+ # of tS transitions, :delta of places and/or transitions) and returns the
93
+ # corresponding mapping of the recording.
94
+ #
95
+ def reduce_features *args
96
+ Δt = if args.last.is_a? Hash then
97
+ args.last.may_have( :delta_time, syn!: :Δt )
98
+ args.last.delete( :delta_time )
99
+ .tap { args.delete_at( -1 ) if args.last.empty? }
100
+ end
101
+ reduced_features = net.State.features *args
102
+ rf_Record = reduced_features.Record
103
+ reduced_features.new_dataset( type: type ).tap do |dataset|
104
+ ( events >> records ).each_pair do |event, record|
105
+ absent_features = reduced_features - features()
106
+ if absent_features.empty? then # it is a subset
107
+ line = reduced_features.map { |feature| record.fetch feature }
108
+ else # it will require simulation reconstruction
109
+ sim = reconstruct event: event
110
+ if absent_features.any? { |f| f.timed? rescue false } then
111
+ fail ArgumentError, "Reconstruction of timed features requires " +
112
+ "the named arg :delta_time to be given!" unless Δt
113
+ line = reduced_features.map do |feature|
114
+ if absent_features.include? feature then
115
+ if ( feature.timed? rescue false ) then
116
+ feature.extract_from( sim ).( Δt )
117
+ else
118
+ feature.extract_from( sim )
119
+ end
120
+ else
121
+ record.fetch feature
122
+ end
123
+ end
124
+ else
125
+ line = reduced_features.map do |feature|
126
+ if absent_features.include? feature then
127
+ feature.extract_from( sim )
128
+ else
129
+ record.fetch feature
130
+ end
131
+ end
132
+ end
133
+ end
134
+ dataset.update event => rf_Record.load( line )
135
+ end
136
+ end
137
+ end
138
+
139
+ # Returns a subset of this dataset with only the specified marking features
140
+ # identified by the arguments retained. If no arguments are given, all the
141
+ # marking features from the receiver dataset are selected.
142
+ #
143
+ def marking ids=nil
144
+ return reduce_features net.State.marking if ids.nil?
145
+ reduce_features marking: ids
146
+ end
147
+
148
+ # Returns a subset of this dataset with only the specified firing features
149
+ # identified by the arguments retained. If no arguments are given, all the
150
+ # firing features from the receiver dataset are selected.
151
+ #
152
+ def firing *args
153
+ Δt = if args.last.is_a? Hash then
154
+ args.last.may_have( :delta_time, syn!: :Δt )
155
+ args.last.delete( :delta_time )
156
+ .tap { args.delete_at( -1 ) if args.last.empty? }
157
+ end
158
+ if Δt then
159
+ return reduce_features net.State.firing, delta_time: Δt if args.empty?
160
+ reduce_features firing: args.first, delta_time: Δt
161
+ else
162
+ return reduce_features net.State.firing if args.empty?
163
+ reduce_features firing: args.first
164
+ end
165
+ end
166
+
167
+ # Returns a subset of this dataset with only the specified flux features
168
+ # identified by the arguments retained. If no arguments are given, all the
169
+ # flux features from the receiver dataset are selected.
170
+ #
171
+ def flux ids=nil
172
+ return reduce_features net.State.flux if ids.nil?
173
+ reduce_features flux: ids
174
+ end
175
+
176
+ # Returns a subset of this dataset with only the specified gradient features
177
+ # identified by the arguments retained. If no arguments are given, all the
178
+ # gradient features from the receiver dataset are selected.
179
+ #
180
+ def gradient *args
181
+ return reduce_features net.State.gradient if args.empty?
182
+ reduce_features gradient: args
183
+ end
184
+
185
+ # Returns a subset of this dataset with only the specified delta features
186
+ # identified by the arguments retained. If no arguments are given, all the
187
+ # delta features from the receiver dataset are selected.
188
+ #
189
+ def delta *args
190
+ Δt = if args.last.is_a? Hash then
191
+ args.last.may_have( :delta_time, syn!: :Δt )
192
+ args.last.delete( :delta_time )
193
+ .tap { args.delete_at( -1 ) if args.last.empty? }
194
+ end
195
+ if Δt then
196
+ return reduce_features net.State.delta, delta_time: Δt if args.empty?
197
+ reduce_features delta: args, delta_time: Δt
198
+ else
199
+ return reduce_features net.State.delta if args.empty?
200
+ reduce_features delta: args
201
+ end
202
+ end
203
+
204
+ # Outputs the current recording in CSV format.
205
+ #
206
+ def to_csv
207
+ map { |lbl, rec| [ lbl, *rec ].join ',' }.join "\n"
208
+ end
209
+
210
+ # Plots the dataset.
211
+ #
212
+ def plot( time: nil, **nn )
213
+ events = events()
214
+ data_ss = series
215
+ x_range = if time.is_a? Range then
216
+ "[#{time.begin}:#{time.end}]"
217
+ else
218
+ "[-0:#{SY::Time.magnitude( time ).amount rescue time}]"
219
+ end
220
+
221
+ Gnuplot.open do |gp|
222
+ Gnuplot::Plot.new gp do |plot|
223
+ plot.xrange x_range
224
+ plot.title nn[:title] || "#{net} plot"
225
+ plot.ylabel nn[:ylabel] || "Values"
226
+ plot.xlabel nn[:xlabel] || "Time [s]"
227
+
228
+ features.labels.zip( data_ss )
229
+ .each { |label, data_array|
230
+ plot.data << Gnuplot::DataSet.new( [ events, data_array ] ) { |ds|
231
+ ds.with = "linespoints"
232
+ ds.title = label
233
+ }
234
+ }
235
+ end
236
+ end
237
+ end
238
+ end # YPetri::Net::Dataset
@@ -0,0 +1,63 @@
1
+ # A mixin catering to the net's own state (ie. marking owned by the place
2
+ # instances themselves) and its features.
3
+ #
4
+ module YPetri::Net::OwnState
5
+ # State owned by the net. More precisely, an instance of the Net::State class,
6
+ # which is an Array subclass, containing the markings owned by the net's
7
+ # places as its elements.
8
+ #
9
+ def state
10
+ State().new( marking )
11
+ end
12
+
13
+ # If no argument is supplied, the method returns the array of the markings
14
+ # owned by the net's places. If an array of place identifiers is supplied,
15
+ # the return value is the array of the markings owned by those places.
16
+ #
17
+ def marking place_ids=nil
18
+ return marking( pp ) if place_ids.nil?
19
+ place_ids.map { |id| place( id ).marking }
20
+ end
21
+
22
+ # Takes an array of tS transition identifiers as an optional argument, and
23
+ # returns the array of their firing under current net state. If no argument
24
+ # is supplied, the net is required to contain no TS transtions, and the
25
+ # method returns the array of firing of all net's tS transitions.
26
+ #
27
+ def firing transition_ids=nil
28
+ if transition_ids.nil? then
29
+ fail TypeError, "Method #firing with no arguments is ambiguous for " +
30
+ "nets with TS transitions!" if timed?
31
+ firing( tS_tt )
32
+ else
33
+ transition_ids.map { |id| tS_transition( id ).firing }
34
+ end
35
+ end
36
+
37
+ # Takes an array of TS transition identifiers as an optional argument, and
38
+ # returns the array of their fluxes under current net state. If no argument
39
+ # is supplied, the array of fluxes of all net's TS transitions is returned.
40
+ #
41
+ def flux transition_ids=nil
42
+ return flux TS_tt() if transition_ids.nil?
43
+ transition_ids.map { |id| TS_transition( id ).flux }
44
+ end
45
+
46
+ # Takes an array of place identifiers, and a named argument +:transitions+,
47
+ # and returns the array of the place gradient contribution by the indicated
48
+ # transitions. The +:transitions+ argument defaults to all the transitions,
49
+ # place identifiers default to all the places. The net must be timed.
50
+ #
51
+ #
52
+ def gradient place_ids=pp, transitions: tt
53
+ fail NotImplementedError
54
+ end
55
+
56
+ # Takes an array of place identifier, and a named argument +:transitions+,
57
+ # and returns the array of the place delta contribution by the indicated
58
+ # transitions.
59
+ #
60
+ def delta place_ids=nil, transitions: tt
61
+ fail NotImplementedError
62
+ end
63
+ end # module YPetri::Net::OwnState
@@ -1,88 +1,115 @@
1
1
  # encoding: utf-8
2
2
 
3
- class YPetri::Net::State
4
- class Feature
5
- # Change of a Petri net place caused by a certain set of transitions.
6
- #
7
- class Delta < Feature
8
- attr_reader :place, :transitions, :step
3
+ # Change of a Petri net place caused by a certain set of transitions.
4
+ #
5
+ class YPetri::Net::State::Feature::Delta < YPetri::Net::State::Feature
6
+ attr_reader :place, :transitions, :step
9
7
 
10
- class << self
11
- def parametrize *args
12
- Class.instance_method( :parametrize ).bind( self ).( *args ).tap do |ç|
13
- # First, prepare the hash of instances.
14
- hsh = Hash.new do |ꜧ, id|
15
- puts "Delta received:"
16
- p id
17
- puts "of class #{id.class}, with ancestors:"
18
- p id.class.ancestors
19
- if id.is_a? Delta then
20
- ꜧ[ [ id.place, transitions: id.transitions.sort( &:object_id ) ] ]
8
+ class << self
9
+ def parametrize *args
10
+ Class.instance_method( :parametrize ).bind( self ).( *args ).tap do |ç|
11
+ # First, prepare the hash of instances.
12
+ hsh = Hash.new do |ꜧ, id|
13
+ if id.is_a? self then # missing key "id" is a Delta instance
14
+ ꜧ[ [ id.place, transitions: id.transitions.sort( &:object_id ) ] ]
15
+ else
16
+ p = id.fetch( 0 )
17
+ tt = id.fetch( 1 ).fetch( :transitions ) # value of :transitions key
18
+ if p.is_a? ç.net.Place and tt.all? { |t| t.is_a? ç.net.Transition }
19
+ if tt == tt.sort then
20
+ # Cache the instance.
21
+ ꜧ[ id ] = if tt.all? &:timed? then
22
+ ç.timed( *id )
23
+ elsif tt.all? &:timeless? then
24
+ ç.timeless( *id )
25
+ else
26
+ fail TypeError, "Net::State::Feature::Delta only " +
27
+ "admits the transition sets that are either " +
28
+ "all timed, or all timeless!"
29
+ end
21
30
  else
22
- puts "here"
23
- p id
24
- puts "id size is #{id.size}"
25
- p = id.fetch( 0 )
26
- tt = id
27
- .fetch( 1 )
28
- .fetch( :transitions )
29
- if p.is_a? ç.net.Place and tt.all? { |t| t.is_a? ç.net.Transition }
30
- if tt == tt.sort then
31
- ꜧ[ id ] = ç.__new__( *id )
32
- else
33
- ꜧ[ [ p, transitions: tt.sort ] ]
34
- end
35
- else
36
- ꜧ[ [ ç.net.place( p ), transitions: ç.net.transitions( tt ) ] ]
37
- end
31
+ ꜧ[ [ p, transitions: tt.sort ] ]
38
32
  end
33
+ else # convert place and transition ids to places and transitions
34
+ ꜧ[ [ ç.net.place( p ), transitions: ç.net.transitions( tt ) ] ]
39
35
  end
40
- # And then, assign it to the :@instances variable.
41
- ç.instance_variable_set :@instances, hsh
42
36
  end
43
37
  end
38
+ # And then, assign it to the :@instances variable.
39
+ ç.instance_variable_set :@instances, hsh
40
+ end
41
+ end
44
42
 
45
- attr_reader :instances
43
+ attr_reader :instances
46
44
 
47
- alias __new__ new
45
+ alias __new__ new
48
46
 
49
- def new *args
50
- return instances[ *args ] if args.size == 1
51
- instances[ args ]
52
- end
47
+ # Timed delta feature constructor. Takes a place, and an array of timed
48
+ # transition identifiers supplied as +:transitions: parameter.
49
+ #
50
+ def timed place, transitions: net.T_tt
51
+ __new__( place, transitions: net.T_tt( transitions ) )
52
+ .tap { |inst| inst.instance_variable_set :@timed, true }
53
+ end
53
54
 
54
- def of *args
55
- new *args
56
- end
57
- end
55
+ # Timeless delta feature constructor. Takes a place, and an array of
56
+ # timeless transition identifiers as +:transitions: parameter.
57
+ #
58
+ def timeless place, transitions: net.t_tt
59
+ __new__( place, transitions: net.t_tt( transitions ) )
60
+ .tap { |inst| inst.instance_variable_set :@timed, false }
61
+ end
58
62
 
59
- def initialize place, transitions: net.tt
60
- @place = net.place( place )
61
- @transitions = net.transitions( transitions )
62
- end
63
+ # Constructor #new is redefined to use instance cache.
64
+ #
65
+ def new *args
66
+ return instances[ *args ] if args.size == 1
67
+ instances[ args ]
68
+ end
69
+ alias of new
70
+ end
63
71
 
64
- def extract_from arg, **nn
65
- # **nn is here because of timed / timeless possibility, where
66
- # **nn would contain :step named argument.
67
- case arg
68
- when YPetri::Simulation then
69
- _T = arg.send( :T_transitions, transitions )
70
- _t = arg.send( :t_transitions, transitions )
71
- if _T.empty? then _t.delta.fetch( place ) else # time step is required
72
- _t.delta.fetch( place ) + _T.delta( nn[:step] ).fetch( place )
73
- end
74
- else
75
- fail TypeError, "Argument type not supported!"
76
- end
77
- end
72
+ def initialize place, transitions: net.tt
73
+ @place = net.place( place )
74
+ @transitions = net.transitions( transitions )
75
+ end
78
76
 
79
- def to_s
80
- place.name
77
+ # Extracts the value of this feature from the supplied target
78
+ # (eg. a simulation).
79
+ #
80
+ def extract_from arg, **nn
81
+ # **nn is here because of timed / timeless possibility, where
82
+ # **nn would contain :step named argument.
83
+ case arg
84
+ when YPetri::Simulation then
85
+ if timed? then
86
+ tt = arg.send( :T_transitions, transitions )
87
+ -> Δt { tt.delta( Δt ).fetch( place ) }
88
+ else
89
+ arg.send( :t_transitions, transitions ).delta.fetch( place )
81
90
  end
91
+ else
92
+ fail TypeError, "Argument type not supported!"
93
+ end
94
+ end
82
95
 
83
- def label
84
- "∂:#{place.name}:#{transitions.size}tt"
85
- end
86
- end # class Delta
87
- end # class Feature
88
- end # YPetri::Net::State
96
+ # Is the delta feature timed?
97
+ #
98
+ def timed?
99
+ @timed
100
+ end
101
+
102
+ # Opposite of +#timed?+.
103
+ #
104
+ def timeless?
105
+ ! timed?
106
+ end
107
+
108
+ def to_s
109
+ place.name
110
+ end
111
+
112
+ def label
113
+ "∂:#{place.name}:#{transitions.size}tt"
114
+ end
115
+ end # class YPetri::Net::State::Feature::Delta
@@ -1,57 +1,54 @@
1
1
  # encoding: utf-8
2
- class YPetri::Net::State
3
- class Feature
4
- # Firing of a Petri net tS transition.
5
- #
6
- class Firing < Feature
7
- attr_reader :transition
8
-
9
- class << self
10
- def parametrize *args
11
- Class.instance_method( :parametrize ).bind( self ).( *args ).tap do |ç|
12
- ç.instance_variable_set( :@instances,
13
- Hash.new do |hsh, id|
14
- case id
15
- when Firing then
16
- hsh[ id.transition ]
17
- when ç.net.Transition then
18
- hsh[ id ] = ç.__new__( id )
19
- else
20
- hsh[ ç.net.transition( id ) ]
21
- end
22
- end )
23
- end
24
- end
25
-
26
- attr_reader :instances
27
-
28
- alias __new__ new
29
-
30
- def new id
31
- instances[ id ]
32
- end
33
-
34
- def of id
35
- new id
36
- end
37
- end
38
-
39
- def initialize transition
40
- @transition = net.transition( transition )
41
- end
42
-
43
- def extract_from arg, **nn
44
- case arg
45
- when YPetri::Simulation then
46
- arg.send( :tS_transitions, [ transition ] ).firing.first
47
- else
48
- fail TypeError, "Argument type not supported!"
49
- end
50
- end
51
2
 
52
- def label
53
- "f:#{transition.name}"
3
+ # Firing of a Petri net tS transition.
4
+ #
5
+ class YPetri::Net::State::Feature::Firing < YPetri::Net::State::Feature
6
+ attr_reader :transition
7
+
8
+ class << self
9
+ def parametrize *args
10
+ Class.instance_method( :parametrize ).bind( self ).( *args ).tap do |ç|
11
+ ç.instance_variable_set( :@instances,
12
+ Hash.new do |hsh, id|
13
+ case id
14
+ when self then
15
+ hsh[ id.transition ]
16
+ when ç.net.Transition then
17
+ hsh[ id ] = ç.__new__( id )
18
+ else
19
+ hsh[ ç.net.transition( id ) ]
20
+ end
21
+ end )
54
22
  end
55
- end # class Firing
56
- end # class Feature
57
- end # YPetri::Net::State
23
+ end
24
+
25
+ attr_reader :instances
26
+
27
+ alias __new__ new
28
+
29
+ def new id
30
+ instances[ id ]
31
+ end
32
+
33
+ def of id
34
+ new id
35
+ end
36
+ end
37
+
38
+ def initialize transition
39
+ @transition = net.transition( transition )
40
+ end
41
+
42
+ def extract_from arg, **nn
43
+ case arg
44
+ when YPetri::Simulation then
45
+ arg.send( :tS_transitions, [ transition ] ).firing.first
46
+ else
47
+ fail TypeError, "Argument type not supported!"
48
+ end
49
+ end
50
+
51
+ def label
52
+ "f:#{transition.name}"
53
+ end
54
+ end # YPetri::Net::State::Feature::Firing