y_petri 2.1.3 → 2.1.6

Sign up to get free protection for your applications and to get access to all the features.
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