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
@@ -1,50 +1,157 @@
1
- class YPetri::Net::State
2
- class Features
3
- # A collection of values for a given set of state features.
1
+ # encoding: utf-8
2
+
3
+ # A collection of values for a given set of state features.
4
+ #
5
+ class YPetri::Net::State::Features::Record < Array
6
+ class << self
7
+ delegate :State,
8
+ :net,
9
+ to: "Features()"
10
+
11
+ # Constructs a new Record object from a given collection of values.
4
12
  #
5
- class Record < Array
6
- class << self
7
- delegate :State,
8
- :net,
9
- to: "Features()"
10
-
11
- # Construcs a new Record object from a given collection of values.
12
- #
13
- def load values
14
- new( values.dup )
15
- end
16
- end
13
+ def load values
14
+ new( values.dup )
15
+ end
16
+ end
17
17
 
18
- delegate :Features,
19
- :State,
20
- :net,
21
- :features,
22
- to: "self.class"
18
+ delegate :Features,
19
+ :State,
20
+ :net,
21
+ :features,
22
+ to: "self.class"
23
23
 
24
- # Outputs the record as a plain array.
25
- #
26
- def dump precision: nil
27
- features.map { |f| fetch( f ).round( precision ) }
28
- end
24
+ # Outputs the record as a plain array.
25
+ #
26
+ def dump precision: nil
27
+ features.map { |f| fetch( f ).round( precision ) }
28
+ end
29
29
 
30
- # Returns an identified feature, or fails.
31
- #
32
- def fetch feature
33
- super begin
34
- Integer( feature )
35
- rescue TypeError
36
- features.index State().feature( feature )
37
- end
38
- end
30
+ # Returns an identified feature, or fails.
31
+ #
32
+ def fetch feature
33
+ super begin
34
+ Integer( feature )
35
+ rescue TypeError
36
+ features.index State().feature( feature )
37
+ end
38
+ end
39
+
40
+ # Returns the state instance implied by the receiver record, and a set of
41
+ # complementary marking clamps supplied as the argument.
42
+ #
43
+ def state marking_clamps: {}
44
+ cc = marking_clamps.with_keys { |k| net.place k }.with_values! do |v|
45
+ case v
46
+ when YPetri::Place then v.marking
47
+ when ~:call then v.call
48
+ else v end
49
+ end
50
+ msg = "Marking clamps given in the argument taken together with this " +
51
+ "record's markings must complete the full state of the net!"
52
+ fail TypeError, msg unless
53
+ features.marking.map( &:place ) + net.places( cc.keys ) == net.places
54
+ State().new net.places.map do |place|
55
+ begin; cc.fetch place; rescue IndexError; fetch marking( place ) end
56
+ end
57
+ end
39
58
 
40
- # Returns the state instance implied by the receiver record, and a set of
41
- # complementary marking clamps supplied as the argument.
42
- #
43
- def state marking_clamps: {}
44
- State.new self, marking_clamps: marking_clamps
59
+ # Given a set of marking clamps complementary to the marking features of this
60
+ # record, reconstructs a Simulation instance with the corresponding state.
61
+ # If the net is timed, or if the construction of the simulation from the net
62
+ # has need for any special settings, these must be supplied to this method.
63
+ # (Timed nets eg. require +:time+ named argument for successful construction.)
64
+ #
65
+ def reconstruct marking_clamps: {}, **settings
66
+ net.simulation marking_clamps: {}, marking: marking, **settings
67
+ end
68
+
69
+ # Expects a marking feature identifier (place identifier or Marking instance),
70
+ # and returns the value for that feature in this record. If an array of
71
+ # marking feature identifiers is supplied, it is mapped to the array of
72
+ # corresponding values. If no argument is given, values from this record for
73
+ # all the present marking features are returned.
74
+ #
75
+ def marking id=nil
76
+ return marking( features.marking ) if id.nil?
77
+ case id
78
+ when Array then id.map { |id| marking id }
79
+ else fetch( features.marking id ) end
80
+ end
81
+
82
+ # Expects a flux feature identifier (transition identifier or Flux instance),
83
+ # and returns the value for that feature in this record. If an array of flux
84
+ # feature identifiers is supplied, it is mapped to the array of corresponding
85
+ # values. If no argument is given, values from this record for all the present
86
+ # flux features are returned.
87
+ #
88
+ def flux id=nil
89
+ return flux( features.flux ) if id.nil?
90
+ case id
91
+ when Array then id.map { |id| flux net.transition( id ) }
92
+ else fetch( features.flux id ) end
93
+ end
94
+
95
+ # Expects a firing feature identifier (transition identifier or Firing
96
+ # instance), and returns the value for that feature in this record. If an
97
+ # array of firing feature identifiers is supplied, it is mapped to the array
98
+ # of corresponding values. If no argument is given, values from this record
99
+ # for all the present flux features are returned.
100
+ #
101
+ def firing id=nil
102
+ return firing( features.firing ) if id.nil?
103
+ case id
104
+ when Array then id.map { |id| firing net.transition( id ) }
105
+ else fetch( features.firing id ) end
106
+ end
107
+
108
+ # Expects a gradient feature identifier (place identifier, or Gradient
109
+ # instance), qualified by an array of transitions (named argument
110
+ # +:transitions+, defaults to all timed transitions in the net), and returns
111
+ # the value for that feature in this record. If an array of gradient feature
112
+ # identifiers is supplied, it is mapped to the array of corresponding values.
113
+ # If no gradient feature identifier is given, values from this record for all
114
+ # the present gradient features are returned.
115
+ #
116
+ def gradient id=nil, transitions: nil
117
+ if id.nil? then
118
+ return gradient( features.gradient ) if transitions.nil?
119
+ gradient( features.gradient.select do |f|
120
+ f.transitions == transitions.map { |t| net.transition t }
121
+ end )
122
+ else
123
+ return gradient( id, transitions: net.T_tt ) if transitions.nil?
124
+ case id
125
+ when Array then
126
+ pl.map { |id| gradient id, transitions: transitions }
127
+ else
128
+ fetch( features.gradient id, transitions: transitions )
45
129
  end
130
+ end
131
+ end
46
132
 
47
- delegate :reconstruct, to: :state
48
- end # class Record
49
- end # class Features
50
- end # YPetri::Net::State
133
+ # Expects a gradient feature identifier (place identifier, or Delta instance),
134
+ # qualified by an array of transitions (named argument +:transitions+,
135
+ # defaults to all timed transitions in the net), and returns the value for
136
+ # that feature in this record. If an array of delta feature identifiers is
137
+ # supplied, it is mapped to the array of corresponding values. If no delta
138
+ # feature identifier is given, values from this record for all the present
139
+ # delta features are returned.
140
+ #
141
+ def delta pl=nil, transitions: nil
142
+ if id.nil? then
143
+ return delta( features.delta ) if transitions.nil?
144
+ delta( features.delta.select do |f|
145
+ f.transitions == transitions.map { |t| net.transition t }
146
+ end )
147
+ else
148
+ return delta( id, transitions: net.tt ) if transitions.nil?
149
+ case id
150
+ when Array then
151
+ id.map { |id| delta id, transitions: transitions }
152
+ else
153
+ fetch( features.delta id, transitions: net.tt( transitions ) )
154
+ end
155
+ end
156
+ end
157
+ end # class YPetri::Net::State::Features::Record
@@ -1,126 +1,282 @@
1
1
  # encoding: utf-8
2
2
 
3
- class YPetri::Net::State
4
- # A set of state features.
5
- #
6
- class Features < Array
7
- require_relative 'features/record'
8
- require_relative 'features/dataset'
9
-
10
- class << self
11
- # Customization of the parametrize method for the Features class: Its
12
- # dependents Record and Dataset are also parametrized.
13
- #
14
- def parametrize parameters
15
- Class.new( self ).tap do |subclass|
16
- parameters.each_pair { |symbol, value|
17
- subclass.define_singleton_method symbol do value end
18
- }
19
- subclass.param_class( { Record: Record, Dataset: Dataset },
20
- with: { Features: subclass } )
21
- end
22
- end
23
-
24
- delegate :net,
25
- :Feature,
26
- :feature,
27
- to: "State()"
28
-
29
- delegate :Marking,
30
- :Firing,
31
- :Gradient,
32
- :Flux,
33
- :Delta,
34
- to: "Feature()"
35
-
36
- delegate :load, to: :Record
37
-
38
- alias __new__ new
39
-
40
- def new features
41
- ff = features.map &method( :feature )
42
- __new__( ff ).tap do |inst|
43
- # Parametrize them <em>one more time</em> with Features instance.
44
- # Banged version of #param_class! ensures that #Record, #Dataset
45
- # methods are shadowed.
46
- inst.param_class!( { Record: Record(), Dataset: Dataset() },
47
- with: { features: inst } )
48
- end
49
- end
50
-
51
- def marking places=net.pp
52
- new net.pp( places ).map { |p| Marking( p ) }
53
- end
54
-
55
- def firing transitions=net.tS_tt
56
- new net.tS_tt( transitions ).map { |t| Firing( t ) }
57
- end
58
-
59
- def gradient places=net.pp, transitions: net.T_tt
60
- tt = net.T_tt( transitions )
61
- new net.pp( places ).map { |p|
62
- Gradient( p, transitions: tt )
63
- }
64
- end
65
-
66
- def flux transitions=net.TS_tt
67
- new net.TS_tt( transitions ).map { |t| Flux( t ) }
68
- end
3
+ # A set of state features.
4
+ #
5
+ class YPetri::Net::State::Features < Array
6
+ require_relative 'features/record'
69
7
 
70
- def delta places=net.pp, transitions: net.tt
71
- transitions = net.tt( transitions )
72
- new net.pp( places ).map { |p|
73
- Delta( p, transitions: transitions )
8
+ class << self
9
+ # Customization of the parametrize method for the Features class: Its
10
+ # dependents Record and Dataset are also parametrized.
11
+ #
12
+ def parametrize parameters
13
+ Class.new( self ).tap do |ç|
14
+ parameters.each_pair { |symbol, value|
15
+ ç.define_singleton_method symbol do value end
74
16
  }
17
+ ç.param_class( { Record: Record,
18
+ Dataset: YPetri::Net::DataSet },
19
+ with: { Features: ç } )
75
20
  end
76
21
  end
77
22
 
78
- delegate :State,
79
- :net,
23
+ delegate :net,
80
24
  :Feature,
81
25
  :feature,
82
- :Marking,
26
+ to: "State()"
27
+
28
+ delegate :Marking,
83
29
  :Firing,
84
30
  :Gradient,
85
31
  :Flux,
86
32
  :Delta,
87
- :load,
88
- to: "self.class"
33
+ to: "Feature()"
89
34
 
90
- # Extracts the features from a given target
91
- #
92
- def extract_from target, **nn
93
- Record().new( map { |feature| feature.extract_from( target, **nn ) } )
35
+ delegate :load, to: "Record()"
36
+
37
+ alias __new__ new
38
+
39
+ def new features
40
+ array = features.map &method( :feature )
41
+ __new__( array ).tap do |inst|
42
+ # Parametrize them <em>one more time</em> with Features instance.
43
+ # Banged version of #param_class! ensures that #Record, #Dataset
44
+ # methods are shadowed.
45
+ inst.param_class!( { Record: Record(), Dataset: Dataset() },
46
+ with: { features: inst } )
47
+ end
94
48
  end
95
49
 
96
- # Constructs a new dataset from these features.
50
+ # Takes an array of marking feature identifiers (places, Marking instances),
51
+ # and returns the corresponding array of marking features valid for the
52
+ # current net. If no argument is given, an array of all the marking features
53
+ # of the current net is returned.
97
54
  #
98
- def new_dataset
99
- Dataset().new
55
+ def marking arg=nil
56
+ return marking net.pp if arg.nil?
57
+ new arg.map { |id| Marking id }
100
58
  end
101
59
 
102
- # Feature summation -- of feature class.
60
+ # Takes an array of firing feature identifiers (transitions, Firing
61
+ # instances), and returns the corresponding array of firing features valid
62
+ # for the current net. If no argument is given, an array of all the firing
63
+ # features of the current net is returned.
103
64
  #
104
- def + other
105
- self.class.new( super )
65
+ def firing arg=nil
66
+ return firing net.tS_tt if arg.nil?
67
+ new arg.map { |id| Firing id }
106
68
  end
107
69
 
108
- # Feature summation -- of feature class.
70
+ # Takes an array of gradient feature identifiers (places, Marking instances),
71
+ # qualified by an array of transitions (named argument +:transitions+,
72
+ # defaults to all the timed transitions in the net), and returns the
73
+ # corresponding array of gradient features valid for the current net. If no
74
+ # argument is given, an array of all the gradient features qualified by the
75
+ # +:transitions+ argument is returned.
109
76
  #
110
- def - other
111
- self.class.new( super )
77
+ def gradient arg=nil, transitions: nil
78
+ if arg.nil? then
79
+ return gradient net.pp, transitions: net.T_tt if transitions.nil?
80
+ gradient net.pp, transitions: transitions
81
+ else
82
+ return new arg.map { |id| Gradient id } if transitions.nil?
83
+ new arg.map { |id| Gradient id, transitions: transitions }
84
+ end
112
85
  end
113
86
 
114
- # Feature summation -- of feature class.
87
+ # Takes an array of flux feature identifiers (transitions, Flux instances),
88
+ # and returns the corresponding array of flux features valid for the current
89
+ # net. If no argument is given, an array of all the flux features of the
90
+ # current net is returned.
115
91
  #
116
- def * other
117
- self.class.new( super )
92
+ def flux arg=nil
93
+ return flux net.TS_tt if arg.nil?
94
+ new arg.map { |t| Flux t }
118
95
  end
119
96
 
120
- # Feature labels.
97
+ # Takes an array of delta feature identifiers (places, Delta instances),
98
+ # qualified by an array of transitions (named argument +:transitions+,
99
+ # defaults to all the transitions in the net), and returns the corresponding
100
+ # array of delta features valid for the current net. If no argument is
101
+ # given, an array of all the delta features qualified by the +:transitions+
102
+ # argument is returned.
121
103
  #
122
- def labels
123
- map &:label
104
+ def delta arg=nil, transitions: nil
105
+ if arg.nil? then
106
+ return delta net.pp, transitions: net.tt if transitions.nil?
107
+ delta net.pp, transitions: transitions
108
+ else
109
+ return new arg.map { |id| Delta id } if transitions.nil?
110
+ new arg.map { |id| Delta id, transitions: transitions }
111
+ end
112
+ end
113
+ end
114
+
115
+ delegate :State,
116
+ :net,
117
+ :Feature,
118
+ :feature,
119
+ :Marking,
120
+ :Firing,
121
+ :Gradient,
122
+ :Flux,
123
+ :Delta,
124
+ to: "self.class"
125
+
126
+ delegate :load,
127
+ to: "Record()"
128
+
129
+ alias new_record load
130
+
131
+ # Extracts the features from a given target
132
+ #
133
+ def extract_from target, **nn
134
+ new_record( map { |feature| feature.extract_from( target, **nn ) } )
135
+ end
136
+
137
+ # Constructs a new dataset from these features.
138
+ #
139
+ def new_dataset *args, &blk
140
+ Dataset().new *args, &blk
141
+ end
142
+
143
+ # Feature summation -- of feature class.
144
+ #
145
+ def + other
146
+ self.class.new( super )
147
+ end
148
+
149
+ # Feature summation -- of feature class.
150
+ #
151
+ def - other
152
+ self.class.new( super )
153
+ end
154
+
155
+ # Feature summation -- of feature class.
156
+ #
157
+ def * other
158
+ self.class.new( super )
159
+ end
160
+
161
+ # Feature labels.
162
+ #
163
+ def labels
164
+ map &:label
165
+ end
166
+
167
+ # Expects a hash identifying a set of features, that is a subset of the
168
+ # current set of features.
169
+ #
170
+ def reduce_features features
171
+ net.State.features( features ).tap do |ff|
172
+ msg = "The argument must identify a subset of the current feature set!"
173
+ fail TypeError, msg unless ( ff - self ).empty?
174
+ end
175
+ end
176
+
177
+ # Returns the subset of marking features.
178
+
179
+
180
+ # Expects a marking feature identifier (place identifier or Marking instance),
181
+ # and returns the corresponding feature from this feature set. If an array of
182
+ # marking feature identifiers is supplied, it is mapped to the array of
183
+ # corresponding features from this feature set. If no argument is given, all
184
+ # the marking features from this set are returned.
185
+ #
186
+ def marking id=nil
187
+ return marking( select { |f| f.is_a? Marking() } ) if id.nil?
188
+ case id
189
+ when Array then self.class.new( id.map { |id| marking id } )
190
+ else
191
+ Marking( id ).tap do |feature|
192
+ include? feature or
193
+ fail KeyError, "No marking feature '#{id}' in this feature set!"
194
+ end
195
+ end
196
+ end
197
+
198
+ # Expects a firing feature idenfier (tS transition identifier, or Firing
199
+ # instance), and returns the corresponding feature from this feature set. If
200
+ # an array of firing feature identifiers is supplied, it is mapped to the
201
+ # array of corresponding features from this feature set. If no argument is
202
+ # given, all the firing features from this set are returned.
203
+ #
204
+ def firing id=nil
205
+ return firing( select { |f| f.is_a? Firing() } ) if id.nil?
206
+ case id
207
+ when Array then self.class.new( id.map { |id| firing id } )
208
+ else
209
+ Firing( id ).tap do |feature|
210
+ include? feature or
211
+ fail KeyError, "No firing feature '#{id}' in this feature set!"
212
+ end
213
+ end
214
+ end
215
+
216
+ # Expects a flux feature identifier (TS transition identifier, or Flux
217
+ # instance), and returns the corresponding feature from this feature set. If
218
+ # an array of flux feature identifiers is supplied, it is mapped to the array
219
+ # of corresponding features from this feature set. If no argument is given,
220
+ # all the flux features from this set are returned.
221
+ #
222
+ def flux id=nil
223
+ return flux( select { |f| f.is_a? Flux() } ) if id.nil?
224
+ case id
225
+ when Array then self.class.new( id.map { |id| flux id } )
226
+ else
227
+ Flux( id ).tap do |feature|
228
+ include? feature or
229
+ fail KeyError, "No flux feature '#{id}' in this feature set!"
230
+ end
231
+ end
232
+ end
233
+
234
+ # Expects a gradient feature identifier (place identifier, or Gradient
235
+ # instance), qualified by an array of transitions (named argument
236
+ # +:transitions+, defaults to all timed transitions in the net), and
237
+ # returns the corresponding feature from this feature set. If an array of
238
+ # gradient feature identifiers is supplied, it is mapped to the array of
239
+ # corresponding features from this feature set. If no argument is given,
240
+ # all the gradient features from this feature set are returned.
241
+ #
242
+ def gradient id=nil, transitions: nil
243
+ if id.nil? then
244
+ return gradient( select { |f| f.is_a? Gradient() } ) if transitions.nil?
245
+ gradient.select { |f| f.transitions == net.tt( transitions ) }
246
+ else
247
+ case id
248
+ when Array then
249
+ self.class.new( id.map { |id| gradient id, transitions: transitions } )
250
+ else
251
+ Gradient( id, transitions: transitions ).tap do |feature|
252
+ include? feature or
253
+ fail KeyError, "No gradient feature '#{id}' in this fature set!"
254
+ end
255
+ end
256
+ end
257
+ end
258
+
259
+ # Expects a delta feature identifier (place identifier, or Gradient instance),
260
+ # qualified by an array of transitions (named argument +:transitions+,
261
+ # defaulting to all the transtitions in the net), and returns the
262
+ # corresponding feature from this feature set. If an array of delta feature
263
+ # identifiers is supplied, it is mapped to the array of corresponding features
264
+ # from thie feature set.
265
+ #
266
+ def delta
267
+ if id.nil? then
268
+ return delta( select { |f| f.is_a? Delta() } ) if transitions.nil?
269
+ delta.select { |f| f.transitions == net.tt( transitions ) }
270
+ else
271
+ case id
272
+ when Array then
273
+ self.class.new( id.map { |id| delta id, transitions: transitions } )
274
+ else
275
+ Delta( id, transitions: transitions ).tap do |feature|
276
+ include? feature or
277
+ fail KeyError, "No delta feature '#{id}' in this feature set!"
278
+ end
279
+ end
124
280
  end
125
- end # class Features
126
- end # YPetri::Net::State
281
+ end
282
+ end # YPetri::Net::State::Features