y_petri 2.2.4 → 2.3.2

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 (117) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +675 -0
  3. data/README.md +6 -3
  4. data/Rakefile +1 -1
  5. data/lib/y_petri/agent/{petri_net_related.rb → petri_net_aspect.rb} +34 -10
  6. data/lib/y_petri/agent/{simulation_related.rb → simulation_aspect.rb} +49 -34
  7. data/lib/y_petri/agent.rb +5 -5
  8. data/lib/y_petri/core/guarded.rb +24 -0
  9. data/lib/y_petri/core/timed/euler.rb +4 -8
  10. data/lib/y_petri/core/timed/gillespie.rb +11 -17
  11. data/lib/y_petri/core/timed/methods.rb +23 -0
  12. data/lib/y_petri/core/timed/pseudo_euler.rb +10 -13
  13. data/lib/y_petri/core/timed/quasi_euler.rb +9 -8
  14. data/lib/y_petri/core/timed/runge_kutta.rb +10 -18
  15. data/lib/y_petri/core/timed.rb +6 -14
  16. data/lib/y_petri/core/timeless/methods.rb +15 -0
  17. data/lib/y_petri/core/timeless/pseudo_euler.rb +4 -8
  18. data/lib/y_petri/core/timeless.rb +9 -4
  19. data/lib/y_petri/core.rb +44 -42
  20. data/lib/y_petri/net/data_set.rb +246 -142
  21. data/lib/y_petri/net/node_access.rb +282 -0
  22. data/lib/y_petri/net/own_state.rb +14 -4
  23. data/lib/y_petri/net/state/feature/assignment.rb +123 -0
  24. data/lib/y_petri/net/state/feature/delta.rb +55 -35
  25. data/lib/y_petri/net/state/feature/firing.rb +68 -25
  26. data/lib/y_petri/net/state/feature/flux.rb +9 -2
  27. data/lib/y_petri/net/state/feature/gradient.rb +36 -19
  28. data/lib/y_petri/net/state/feature/marking.rb +10 -5
  29. data/lib/y_petri/net/state/feature.rb +105 -11
  30. data/lib/y_petri/net/state/features/record.rb +144 -99
  31. data/lib/y_petri/net/state/features.rb +327 -200
  32. data/lib/y_petri/net/state.rb +48 -82
  33. data/lib/y_petri/net/visualization.rb +1 -1
  34. data/lib/y_petri/net.rb +62 -47
  35. data/lib/y_petri/place/arcs.rb +44 -0
  36. data/lib/y_petri/place/features.rb +115 -0
  37. data/lib/y_petri/place.rb +62 -29
  38. data/lib/y_petri/simulation/dependency.rb +31 -67
  39. data/lib/y_petri/simulation/feature_set.rb +1 -1
  40. data/lib/y_petri/simulation/initial_marking/access.rb +42 -26
  41. data/lib/y_petri/simulation/marking_clamps/access.rb +22 -17
  42. data/lib/y_petri/simulation/marking_clamps.rb +0 -2
  43. data/lib/y_petri/simulation/marking_vector/access.rb +102 -40
  44. data/lib/y_petri/simulation/marking_vector.rb +35 -37
  45. data/lib/y_petri/simulation/matrix.rb +1 -1
  46. data/lib/y_petri/simulation/node_representation.rb +25 -0
  47. data/lib/y_petri/simulation/nodes/access.rb +78 -0
  48. data/lib/y_petri/simulation/{elements.rb → nodes.rb} +14 -13
  49. data/lib/y_petri/simulation/place_mapping.rb +2 -2
  50. data/lib/y_petri/simulation/place_representation.rb +8 -7
  51. data/lib/y_petri/simulation/places/access.rb +89 -70
  52. data/lib/y_petri/simulation/places/free.rb +1 -1
  53. data/lib/y_petri/simulation/places/types.rb +20 -22
  54. data/lib/y_petri/simulation/places.rb +23 -18
  55. data/lib/y_petri/simulation/recorder.rb +23 -18
  56. data/lib/y_petri/simulation/timed/recorder.rb +19 -11
  57. data/lib/y_petri/simulation/timed.rb +93 -29
  58. data/lib/y_petri/simulation/timeless/recorder.rb +11 -6
  59. data/lib/y_petri/simulation/timeless.rb +13 -3
  60. data/lib/y_petri/simulation/transition_representation/A.rb +24 -4
  61. data/lib/y_petri/simulation/transition_representation/S.rb +11 -1
  62. data/lib/y_petri/simulation/transition_representation/T.rb +1 -1
  63. data/lib/y_petri/simulation/transition_representation/Ts.rb +1 -1
  64. data/lib/y_petri/simulation/transition_representation/a.rb +1 -1
  65. data/lib/y_petri/simulation/transition_representation/s.rb +12 -1
  66. data/lib/y_petri/simulation/transition_representation/t.rb +1 -1
  67. data/lib/y_petri/simulation/transition_representation/tS.rb +1 -1
  68. data/lib/y_petri/simulation/transition_representation/ts.rb +1 -1
  69. data/lib/y_petri/simulation/transition_representation/types.rb +1 -1
  70. data/lib/y_petri/simulation/transition_representation.rb +4 -11
  71. data/lib/y_petri/simulation/transitions/A.rb +17 -2
  72. data/lib/y_petri/simulation/transitions/S.rb +1 -1
  73. data/lib/y_petri/simulation/transitions/T.rb +1 -1
  74. data/lib/y_petri/simulation/transitions/Ts.rb +6 -5
  75. data/lib/y_petri/simulation/transitions/a.rb +1 -1
  76. data/lib/y_petri/simulation/transitions/access.rb +195 -168
  77. data/lib/y_petri/simulation/transitions/s.rb +1 -1
  78. data/lib/y_petri/simulation/transitions/t.rb +1 -1
  79. data/lib/y_petri/simulation/transitions/tS.rb +1 -1
  80. data/lib/y_petri/simulation/transitions/ts.rb +1 -1
  81. data/lib/y_petri/simulation/transitions/types.rb +1 -1
  82. data/lib/y_petri/simulation/transitions.rb +5 -7
  83. data/lib/y_petri/simulation.rb +84 -90
  84. data/lib/y_petri/transition/A.rb +8 -2
  85. data/lib/y_petri/transition/T.rb +25 -2
  86. data/lib/y_petri/transition/arcs.rb +19 -3
  87. data/lib/y_petri/transition/construction_convenience.rb +11 -10
  88. data/lib/y_petri/transition/t.rb +14 -1
  89. data/lib/y_petri/transition/types.rb +6 -1
  90. data/lib/y_petri/transition.rb +9 -12
  91. data/lib/y_petri/version.rb +1 -1
  92. data/lib/y_petri/world/dependency.rb +3 -3
  93. data/lib/y_petri/world/{petri_net_related.rb → petri_net_aspect.rb} +4 -4
  94. data/lib/y_petri/world/simulation_aspect.rb +352 -0
  95. data/lib/y_petri/world.rb +4 -4
  96. data/lib/y_petri.rb +1 -1
  97. data/test/agent_test.rb +2 -1
  98. data/test/examples/demonstrator.rb +4 -1
  99. data/test/examples/demonstrator_2.rb +5 -0
  100. data/test/examples/demonstrator_4.rb +6 -5
  101. data/test/examples/example_2.rb +2 -0
  102. data/test/examples/manual_examples.rb +4 -4
  103. data/test/net_test.rb +457 -54
  104. data/test/place_test.rb +11 -7
  105. data/test/simulation_test.rb +358 -331
  106. data/test/transition_test.rb +11 -10
  107. data/test/world_test.rb +2 -0
  108. data/test/y_petri_test.rb +2 -1
  109. data/y_petri.gemspec +24 -18
  110. metadata +71 -17
  111. data/LICENSE +0 -22
  112. data/lib/y_petri/net/element_access.rb +0 -239
  113. data/lib/y_petri/simulation/element_representation.rb +0 -20
  114. data/lib/y_petri/simulation/elements/access.rb +0 -57
  115. data/lib/y_petri/transition/type.rb +0 -103
  116. data/lib/y_petri/transition/type_information.rb +0 -103
  117. data/lib/y_petri/world/simulation_related.rb +0 -176
data/lib/y_petri/core.rb CHANGED
@@ -1,64 +1,66 @@
1
1
  # encoding: utf-8
2
2
 
3
- require_relative 'core/timed'
4
- require_relative 'core/timeless'
5
-
3
+ # This class represents a simulator.
4
+ #
6
5
  class YPetri::Core
6
+ require_relative 'core/timed'
7
+ require_relative 'core/timeless'
8
+ require_relative 'core/guarded'
9
+
10
+ ★ YPetri::Simulation::Dependency
11
+
7
12
  DEFAULT_METHOD = :pseudo_euler
8
13
 
9
- module Guarded
10
- # Guarded simulation.
14
+ class << self
15
+ # Timed subclass of self.
11
16
  #
12
- def guarded?
13
- true
17
+ def timed
18
+ Class.new self do
19
+ include Timed
20
+ def timed?; true end
21
+ def timeless?; false end
22
+ end
14
23
  end
15
24
 
16
- # Guarded version of the method.
25
+ # Timeless subclass of self.
17
26
  #
18
- def increment_marking_vector( delta )
19
- try "to update marking" do
20
- super( note( "Δ state if tS transitions fire once",
21
- is: Δ_if_tS_fire_once ) +
22
- note( state if tsa transitions fire once",
23
- is: Δ_if_tsa_fire_once ) )
27
+ def timeless
28
+ Class.new self do
29
+ include Timeless
30
+ def timed?; false end
31
+ def timeless?; true end
24
32
  end
25
33
  end
26
34
 
27
- # Guarded version of the method.
35
+ # Vanilla simulator is not guarded.
36
+ #
37
+ def guarded?; false end
38
+
39
+ # Guarded subclass of self (not working yet).
28
40
  #
29
- def A_all_fire!
30
- try "to fire the assignment transitions" do
31
- super
41
+ def guarded
42
+ Class.new self do
43
+ include Guarded
44
+ def guarded?; true end
32
45
  end
33
46
  end
34
47
  end
35
48
 
36
- include YPetri::Simulation::Dependency
37
-
38
- delegate :simulation, to: "self.class"
39
- delegate :alert, to: :recorder
49
+ attr_reader :simulation_method
40
50
 
41
- class << self
42
- alias __new__ new
43
-
44
- def new( method: nil, guarded: false, **nn )
45
- # Alow for named arg. alias :simulation_method
46
- sm = method || nn[:simulation_method] || DEFAULT_METHOD
47
- using_simulation_method( sm, guarded: guarded ).__new__
48
- end
49
-
50
- def using_simulation_method symbol, guarded: false
51
- simulation_method_module = const_get( symbol.to_s.camelize )
52
- # TODO: "guarded" argument not handled yet
53
- Class.new self do prepend( simulation_method_module ) end
54
- end
51
+ def initialize method: nil, guarded: false, **named_args
52
+ @simulation_method = method || DEFAULT_METHOD
53
+ method_init # defined in Timed::Methods and Timeless::Methods
55
54
  end
56
55
 
57
- # Simulation is not guarded by default.
58
- #
59
- def guarded?
60
- false
61
- end
56
+ delegate :simulation,
57
+ :timed?,
58
+ :timeless?,
59
+ :guarded?,
60
+ to: "self.class"
61
+
62
+ delegate :alert!,
63
+ to: :recorder
62
64
 
63
65
  # Delta for free places from timeless transitions.
64
66
  #
@@ -95,6 +97,6 @@ class YPetri::Core
95
97
  # Fires assignment transitions.
96
98
  #
97
99
  def assignment_transitions_all_fire!
98
- simulation.A_assignment_closure.call
100
+ simulation.A_direct_assignment_closure.call
99
101
  end
100
102
  end # class YPetri::Core
@@ -1,6 +1,29 @@
1
1
  # encoding: utf-8
2
2
 
3
- # Dataset is a collection of labeled state records.
3
+ # +DataSet+ is a collection of labeled state records. It is a subclass of +Hash+
4
+ # class, whose keys are known as _events_, and values are data points (arrays)
5
+ # that correspond to saved records (+YPetri::Net::State::Features::Record+) under
6
+ # a given feature set (+YPetri::Net::State::Features+). +DataSet+ class is
7
+ # intended to be parametrized with a specific feature set. Apart from the methods
8
+ # inherited from +Hash+, +YPetri::Net::DataSet+ can load a record at a given
9
+ # event (+#record+ method), reconstruct a simulation at a given event
10
+ # (+#reconstruct+ method), return columns corresponding to features (+#series+
11
+ # method) and perform feature selection (+#marking+, +#firing+, +#flux+,
12
+ # +#gradient+, +#delta+, +#assignment+, and +#reduced_features+ for mixed feature
13
+ # sets). Apart from standard inspection methods, +DataSet+ has methods +#print+
14
+ # and +#plot+ for visual presentation. Also, +DataSet+ has methods specially
15
+ # geared towards records of timed simulations, whose events are points in time.
16
+ # Method +#interpolate+ uses linear interpolation to find the approximate state
17
+ # of the system at some exact time using linear interpolation between the nearest
18
+ # earlier and later data points (which can be accessed respectively by +#floor+
19
+ # and +#ceiling+ methods). Interpolation is used for resampling the set
20
+ # (+#resample+ method).
21
+ #
22
+ # Finally, it is possible that especially professional statisticians have
23
+ # written, or are planning to write, a +DataSet+ class better than this one.
24
+ # If I discover a good +DataSet+ class in the future, I would like to inherit
25
+ # from it or otherwise integrate with it for the purposes of
26
+ # +YPetri::Net::DataSet+.
4
27
  #
5
28
  class YPetri::Net::DataSet < Hash
6
29
  class << self
@@ -19,18 +42,12 @@ class YPetri::Net::DataSet < Hash
19
42
  private :__new__
20
43
 
21
44
  delegate :net, to: :features
22
- delegate :State, to: :net
23
- delegate :Marking, :Firing, :Flux, :Gradient, :Delta,
24
- to: "State()"
25
45
  end
26
46
 
27
47
  alias events keys
28
- alias records values
29
48
 
30
49
  delegate :features,
31
50
  :net,
32
- :State,
33
- :Marking, :Firing, :Flux, :Gradient, :Delta,
34
51
  to: "self.class"
35
52
 
36
53
  attr_reader :type, # more like event_type, idea not matured yet
@@ -78,16 +95,16 @@ class YPetri::Net::DataSet < Hash
78
95
  #
79
96
  def reconstruct at: (fail "No event given!"), **settings
80
97
  # settings may include marking clamps, marking, inital marking...
81
- rec = interpolate( at )
98
+ record = interpolate( at )
82
99
  settings = settings().merge settings if settings()
83
100
  if timed? then
84
- rec.reconstruct time: at, **settings
101
+ record.reconstruct time: at, **settings
85
102
  else
86
- rec.reconstruct **settings
103
+ record.reconstruct **settings
87
104
  end
88
105
  end
89
106
 
90
- # Interpolates the recording an the given point (event). Return value is the
107
+ # Interpolates the recording at the given point (event). Return value is the
91
108
  # Record class instance.
92
109
  #
93
110
  def interpolate( event )
@@ -97,23 +114,24 @@ class YPetri::Net::DataSet < Hash
97
114
  timed? or raise TypeError, "Event #{event} not recorded! (%s)" %
98
115
  "simulation type: #{type.nil? ? 'nil' : type}"
99
116
  # (Remark: #floor, #ceiling supported by timed datasets only)
100
- fe = floor( event )
101
- fail "Event #{event} has no floor!" if fe.nil?
102
- f = Matrix.column_vector record( fe )
103
- ce = ceiling( event )
104
- fail "Event #{event} has no ceiling!" if ce.nil?
105
- c = Matrix.column_vector record( ce )
106
- rslt = f + ( c - f ) / ( ce - fe ) * ( event - fe )
117
+ floor = floor( event )
118
+ fail "Event #{event} has no floor!" if floor.nil?
119
+ fl = Matrix.column_vector record( floor )
120
+ ceiling = ceiling( event )
121
+ fail "Event #{event} has no ceiling!" if ceiling.nil?
122
+ ce = Matrix.column_vector record( ceiling )
123
+ rslt = fl + ( ce - fl ) / ( ceiling - floor ) * ( event - floor )
107
124
  features.load( rslt.column_to_a )
108
125
  end
109
126
  end
127
+ alias at interpolate
110
128
 
111
129
  # Resamples the recording.
112
130
  #
113
- def resample **nn
114
- time_range = nn.may_have( :time_range, syn!: :time ) ||
131
+ def resample **settings
132
+ time_range = settings.may_have( :time_range, syn!: :time ) ||
115
133
  events.first .. events.last
116
- sampling = nn.must_have :sampling
134
+ sampling = settings.must_have :sampling
117
135
  t0, target_time = time_range.begin, time_range.end
118
136
  t = t0
119
137
  o = self.class.new type: type
@@ -137,165 +155,238 @@ class YPetri::Net::DataSet < Hash
137
155
 
138
156
  # Returns the data series for the specified features.
139
157
  #
140
- def series arg=nil
141
-
142
- return records.transpose if arg.nil?
143
- reduce_features( State().features( arg ) ).series
158
+ def series array=nil
159
+ return records.transpose if array.nil?
160
+ reduce_features( net.State.Features array ).series
144
161
  end
145
162
 
146
163
  # Expects a hash of features (:marking (alias :state) of places, :firing
147
- # of tS transitions, :delta of places and/or transitions) and returns the
148
- # corresponding mapping of the recording.
164
+ # of tS transitions, :delta of places and/or transitions, :assignment of
165
+ # A transitions) and returns the corresponding mapping of the recording.
149
166
  #
150
- def reduce_features *args
151
- Δt = if args.last.is_a? Hash then
152
- args.last.may_have( :delta_time, syn!: :Δt )
153
- args.last.delete( :delta_time )
154
- .tap { args.delete_at( -1 ) if args.last.empty? }
155
- end
156
- reduced_features = net.State.features *args
157
- rf_Record = reduced_features.Record
158
- reduced_features.new_dataset( type: type ).tap { |dataset|
159
- ( events >> records ).each_pair { |event, record|
160
- absent_features = reduced_features - features()
161
- if absent_features.empty? then # it is a subset
162
- line = reduced_features.map { |feature| record.fetch feature }
163
- else # it will require simulation reconstruction
164
- sim = reconstruct at: event
165
- if absent_features.any? { |f| f.timed? rescue false } then
166
- fail ArgumentError, "Reconstruction of timed features requires " +
167
- "the named arg :delta_time to be given!" unless Δt
168
- line = reduced_features.map do |feature|
169
- if absent_features.include? feature then
170
- if ( feature.timed? rescue false ) then
171
- feature.extract_from( sim ).( Δt )
172
- else
173
- feature.extract_from( sim )
174
- end
175
- else
176
- record.fetch feature
177
- end
178
- end
179
- else
180
- line = reduced_features.map do |feat|
181
- if absent_features.include? feat then
182
- feat.extract_from( sim )
183
- else record.fetch( feat ) end
184
- end
185
- end
186
- end
187
- dataset.update event => rf_Record.load( line )
188
- }
189
- }
167
+ def reduce_features array=nil, **named_args
168
+ delta_time_given = named_args.has? :delta_time, syn!: :Δt
169
+ Δt = named_args.delete :delta_time
170
+ ff = net.State.Features[ *array, **named_args ] # reduced feature set
171
+ absent = ff - features() # features absent from the current set
172
+ present = ff - absent # features present in the current set
173
+ timedness = true if absent.any? { |f| f.timed? rescue false }
174
+ fail ArgumentError, "Reconstruction of timed features requires Δt to be" +
175
+ "supplied!" unless delta_time_given if timedness
176
+ present_ii =
177
+ present.each_with_object( {} ) { |f, ꜧ| ꜧ[f] = features().index f }
178
+ ds = ff.DataSet.new type: type
179
+ if absent.empty? then # no reconstruction
180
+ ( events >> records ).each_with_object ds do |(event, record), dataset|
181
+ line = record.values_at *ff.map( &present_ii.method( :[] ) )
182
+ dataset.update event => ff.load( line )
183
+ end
184
+ else
185
+ ( events >> records ).each_with_object ds do |(event, record), dataset|
186
+ reconstructed_sim = reconstruct at: event
187
+ line = if timedness then
188
+ ff.map { |f|
189
+ i = present_ii[ f ]
190
+ break record[ i ] if i
191
+ f.extract_from( reconstructed_sim, Δt: Δt )
192
+ }
193
+ else
194
+ ff.map { |f|
195
+ i = present_ii[ f ]
196
+ break record[ i ] if i
197
+ f.extract_from( reconstructed_sim, Δt: Δt )
198
+ }
199
+ end
200
+ dataset.update event => ff.load( line )
201
+ end
202
+ end
190
203
  end
191
204
 
192
- # Returns a subset of this dataset with only the specified marking features
193
- # identified by the arguments retained. If no arguments are given, all the
194
- # marking features from the receiver dataset are selected.
205
+ # Expects an array of marking feature identifiers, and returns a subset of
206
+ # this dataset with only the specified marking features retained.
195
207
  #
196
- def marking ids=nil
197
- return reduce_features net.State.marking if ids.nil?
198
- reduce_features marking: ids
208
+ def Marking array
209
+ reduce_features marking: array
199
210
  end
200
211
 
201
- # Returns a subset of this dataset with only the specified firing features
202
- # identified by the arguments retained. If no arguments are given, all the
203
- # firing features from the receiver dataset are selected.
212
+ # Expects an arbitrary number of marking feature identifiers, and returns a
213
+ # subset of this dataset with only the specified marking features retained.
214
+ # If no arguments are given, all the marking features are assumed.
204
215
  #
205
- def firing *args
206
- Δt = if args.last.is_a? Hash then
207
- args.last.may_have( :delta_time, syn!: :Δt )
208
- args.last.delete( :delta_time )
209
- .tap { args.delete_at( -1 ) if args.last.empty? }
210
- end
211
- if Δt then
212
- return reduce_features net.State.firing, delta_time: Δt if args.empty?
213
- reduce_features firing: args.first, delta_time: Δt
214
- else
215
- return reduce_features net.State.firing if args.empty?
216
- reduce_features firing: args.first
217
- end
216
+ def marking *ids
217
+ return Marking net.State.Features.marking if ids.empty?
218
+ Marking ids
218
219
  end
219
220
 
220
- # Returns a subset of this dataset with only the specified flux features
221
- # identified by the arguments retained. If no arguments are given, all the
222
- # flux features from the receiver dataset are selected.
221
+ # Expects an array of firing feature identifiers, and returns a subset of
222
+ # this dataset with only the specified firing features retained. Named
223
+ # arguments may include +:delta_time+, alias +:Δt+ (for firing of timed
224
+ # transitions).
225
+ #
226
+ def Firing array, **named_args
227
+ reduce_features firing: array, **named_args
228
+ end
229
+
230
+ # Expects an arbitrary number of firing feature identifiers and returns
231
+ # a subset of this dataset with only the specified firing features retained.
232
+ # Named arguments may include +:delta_time+, alias +:Δt+ (for firing of
233
+ # timed transitions).
223
234
  #
224
- def flux ids=nil
225
- return reduce_features net.State.flux if ids.nil?
226
- reduce_features flux: ids
235
+ def firing *ids, **named_args
236
+ return Firing net.State.Features.firing, **named_args if ids.empty?
237
+ Firing ids, **named_args
238
+ end
239
+
240
+ # Expects an array of flux feature identifiers, and returns a subset of
241
+ # this dataset with only the specified flux features retained.
242
+ #
243
+ def Flux array
244
+ reduce_features flux: array
245
+ end
246
+
247
+ # Expects an arbitrary number of flux feature identifiers, and returns
248
+ # a subset of this dataset, with only the specified flux features retained.
249
+ # If no aruments are given, full set of flux features is assumed.
250
+ #
251
+ def flux *ids
252
+ return Flux net.State.Features.flux if ids.empty?
253
+ Flux ids
254
+ end
255
+
256
+ # Expects an array of gradient feature identifiers, optionally qualified by
257
+ # the +:transitions+ named argument, defaulting to all T transitions in the
258
+ # net.
259
+ #
260
+ def Gradient array, transitions: nil
261
+ if transitions.nil? then
262
+ reduce_features gradient: array
263
+ else
264
+ reduce_features gradient: [ *array, transitions: transitions ]
265
+ end
227
266
  end
228
267
 
229
268
  # Returns a subset of this dataset with only the specified gradient features
230
269
  # identified by the arguments retained. If no arguments are given, all the
231
270
  # gradient features from the receiver dataset are selected.
232
271
  #
233
- def gradient *args
234
- return reduce_features net.State.gradient if args.empty?
235
- reduce_features gradient: args
272
+ def gradient *ids, transitions: nil
273
+ return Gradient net.State.Features.gradient, transitions: transitions if
274
+ ids.empty?
275
+ Gradient ids, transitions: transitions
236
276
  end
237
277
 
238
- # Returns a subset of this dataset with only the specified delta features
239
- # identified by the arguments retained. If no arguments are given, all the
240
- # delta features from the receiver dataset are selected.
278
+ # Expects an array of delta feature identifiers, optionally qualified by
279
+ # the +:transitions+ named argument, defaulting to all the transitions in
280
+ # the net.
241
281
  #
242
- def delta *args
243
- Δt = if args.last.is_a? Hash then
244
- args.last.may_have( :delta_time, syn!: :Δt )
245
- args.last.delete( :delta_time )
246
- .tap { args.delete_at( -1 ) if args.last.empty? }
247
- end
248
- if Δt then
249
- return reduce_features net.State.delta, delta_time: Δt if args.empty?
250
- reduce_features delta: args, delta_time: Δt
282
+ def Delta array, transitions: nil, **named_args
283
+ if named_args.has? :delta_time, syn!: :Δt then
284
+ Δt = named_args.delete( :delta_time )
285
+ if transitions.nil? then
286
+ reduce_features delta: array, Δt: Δt
287
+ else
288
+ reduce_features delta: [ *array, transitions: transitions ], Δt: Δt
289
+ end
251
290
  else
252
- return reduce_features net.State.delta if args.empty?
253
- reduce_features delta: args
291
+ if transitions.nil? then
292
+ reduce_features delta: array
293
+ else
294
+ reduce_features delta: [ *array, transitions: transitions ]
295
+ end
254
296
  end
255
297
  end
256
298
 
299
+ # Expects an arbitrary number of ordered arguments identifying delta
300
+ # features, optionally qualified by the +:transitions+ named argument,
301
+ # defaulting to all the transitions in the net.
302
+ #
303
+ def delta *ordered_args, transitions: nil, **named_args
304
+ return Delta( ordered_args, transitions: transitions, **named_args ) unless
305
+ ordered_args.empty?
306
+ return Delta( net.places, **named_args ) if transitions.nil?
307
+ Delta( net.places, transitions: transitions, **named_args )
308
+ end
309
+
310
+ def delta_timed *ordered_args, **named_args
311
+ delta *ordered_args, transitions: net.T_transitions, **named_args
312
+ end
313
+
314
+ def delta_timeless *ordered_args, **named_args
315
+ delta *ordered_args, transitions: net.t_transitions, **named_args
316
+ end
317
+
318
+ # Expects an array of assignment feature identifiers. Returns a subset of this
319
+ # dataset with only the specified assignment features retained.
320
+ #
321
+ def Assignment array
322
+ reduce_features assignment: array
323
+ end
324
+
325
+ # Expects an arbitrary number of assignment feature identifiers as arguments,
326
+ # and returns a subset of this dataset with only the specified assignment
327
+ # features retained. If no arguments are given, all the assignment features
328
+ # are assumed.
329
+ #
330
+ def assignment *ids
331
+ return reduce_features net.State.Features.assignment if args.empty?
332
+ reduce_features assignment: ids
333
+ end
334
+
257
335
  # Outputs the current recording in CSV format.
258
336
  #
259
337
  def to_csv
260
- map { |lbl, rec| [ lbl, *rec ].join ',' }.join "\n"
338
+ require 'csv'
339
+ [ ":event", *features.labels.map( &:to_s ) ].join( ',' ) + "\n" +
340
+ map { |lbl, rec| [ lbl, *rec ].join ',' }.join( "\n" )
261
341
  end
262
342
 
263
- # Plots the dataset. Takes several optional arguments: The list of elements
264
- # can be supplied as optional first ordered argument, which are then converted
265
- # into features using +Net::State::Features.infer_from_elements+ method.
266
- # Similarly, the features to exclude can be specifies as a list of elements
267
- # (or a feature-specifying hash) supplied under +except:+ keyword. Otherwise,
268
- # feature specification can be passed to the method as named arguments. If
269
- # no feature specification is explicitly provided, it is assumed that all the
270
- # features of this dataset are meant to be plotted.
343
+ # Plots the dataset. Takes several optional arguments: The list of nodes can be
344
+ # supplied as optional first ordered argument, which are then converted into
345
+ # features using +Net::State::Features.infer_from_nodes+ method. Similarly,
346
+ # the features to exclude can be specifies as a list of nodes (or a
347
+ # feature-specifying hash) supplied under +except:+ keyword. Otherwise, feature
348
+ # specification can be passed to the method as named arguments. If no feature
349
+ # specification is explicitly provided, it is assumed that all the features of
350
+ # this dataset are meant to be plotted.
271
351
  #
272
- def plot( element_ids=nil, time: nil, except: [], **nn )
352
+ def plot( nodes=nil, except: [], **named_args )
353
+ puts "Hello from plot!"
354
+ nn = named_args
273
355
  time = nn.may_have :time, syn!: :time_range
274
356
  events = events()
275
- if element_ids.nil? then
276
- nn_ff = nn.slice [ :marking, :flux, :firing, :gradient, :delta ]
277
- ff = nn_ff.empty? ? features : net.State.features( nn_ff )
278
- else
279
- ff = net.State.Features.infer_from_elements( element_ids )
280
- end
357
+ # Figure out features.
358
+ ff = if nodes.nil? then
359
+ nn_ff = nn.slice [ :marking, :flux, :firing,
360
+ :gradient, :delta, :assignment ]
361
+ nn_ff.empty? ? features : net.State.Features( nn_ff )
362
+ else
363
+ net.State.Features.infer_from_nodes( nodes )
364
+ end
365
+ # Figure out the features not to plot ("except" features).
281
366
  xff = case except
282
- when Array then net.State.Features.infer_from_elements( except )
283
- when Hash then net.State.features( except )
367
+ when Array then net.State.Features.infer_from_nodes( except )
368
+ when Hash then net.State.Features( except )
284
369
  else
285
370
  fail TypeError, "Wrong type of :except argument: #{except.class}"
286
371
  end
372
+ # Subtract the "except" features from features to plot.
287
373
  ff -= xff
288
- data_ss = series( ff )
289
- x_range = if time.nil? then
374
+ # Convert the feature set into a set of data arrays.
375
+ data_arrays = series( ff )
376
+ # Figure out the x axis range for plotting.
377
+ x_range = if nn.has? :time then
378
+ if time.is_a? Range then
379
+ "[#{time.begin}:#{time.end}]"
380
+ else
381
+ "[-0:#{SY::Time.magnitude( time ).amount rescue time}]"
382
+ end
383
+ else
290
384
  from = events.first || 0
291
- to = events.last && events.last > from ? events.last :
292
- events.first + 1
385
+ to = if events.last and events.last > from then events.last
386
+ else events.first + 1 end
293
387
  "[#{from}:#{to}]"
294
- elsif time.is_a? Range then
295
- "[#{time.begin}:#{time.end}]"
296
- else
297
- "[-0:#{SY::Time.magnitude( time ).amount rescue time}]"
298
388
  end
389
+ # Invoke Gnuplot.
299
390
  Gnuplot.open do |gp|
300
391
  Gnuplot::Plot.new gp do |plot|
301
392
  plot.xrange x_range
@@ -307,10 +398,23 @@ class YPetri::Net::DataSet < Hash
307
398
  plot.title nn[:title] || "#{net} plot"
308
399
  plot.ylabel nn[:ylabel] || "Values"
309
400
  plot.xlabel nn[:xlabel] || "Time [s]"
310
- ff.labels.zip( data_ss ).each do |label, data_array|
311
- plot.data << Gnuplot::DataSet.new( [ events, data_array ] ) { |ds|
312
- ds.with = "linespoints"
313
- ds.title = label
401
+ ff.labels.zip( data_arrays ).each do |label, array|
402
+ # Replace NaN and Infinity with 0.0 and warn about it.
403
+ nan, inf = 0, 0
404
+ array = array.map { |v|
405
+ if v.to_f.infinite? then inf += 1; 0.0
406
+ elsif v.to_f.nan? then nan += 1; 0.0
407
+ else v end
408
+ }
409
+ # Warn.
410
+ nan = nan > 0 ? "#{nan} NaN values" : nil
411
+ inf = inf > 0 ? "#{inf} infinite values" : nil
412
+ msg = "Warning: column #{label} contains %s plotted as 0!"
413
+ warn msg % [ nan, inf ].compact.join( ' and ' ) if nan or inf
414
+ # Finally, plot.
415
+ plot.data << Gnuplot::DataSet.new( [ events, array ] ) { |set|
416
+ set.with = "linespoints"
417
+ set.title = label
314
418
  }
315
419
  end
316
420
  end