y_petri 2.2.4 → 2.3.2

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