Class: YPetri::Net::DataSet

Inherits:
Hash
  • Object
show all
Defined in:
lib/y_petri/net/data_set.rb

Overview

YPetri::Net::DataSet is a collection of labeled state records. It is a subclass of Hash. DataSet keys are known as events, and values are data points. Each of these data points is an array that can be used to reconstruct a record, (an object of YPetri::Net::State::Features::Record class). Each record records a specific feature set (of YPetri::Net::State::Features class). DataSet class is intended to be parametrized with such specific feature set, which indicates the meaning of its data points.

Apart from methods inherited from Hash, DataSet can load a record at a given event (#record method), reconstruct a simulation at a given event (#reconstruct method), return columns corresponding to features (#series method) and perform feature selection (#marking, #firing, #flux, #gradient, #delta, #assignment, and #reduced_features for mixed feature sets). Apart from standard inspection methods, DataSet has methods #print and #plot for visual presentation. Also, DataSet has methods specially geared towards records of timed simulations, whose events are points in time. Method #interpolate uses linear interpolation to find the approximate state of the system at some exact time using linear interpolation between the nearest earlier and later data points (which can be accessed respectively by #floor and #ceiling methods). Interpolation is available to the user, and is also used by DataSet#resample method for resampling the dataset.

Finally, it is possible that especially professional statisticians have written, or are planning to write, a DataSet class better than this one. If I discover a good DataSet class in the future, I would like to inherit from it or otherwise integrate with it for the purposes of DataSet.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#settingsObject (readonly)

TODO: More like event_type, idea not matured yet.



57
58
59
# File 'lib/y_petri/net/data_set.rb', line 57

def settings
  @settings
end

#typeObject (readonly)

TODO: More like event_type, idea not matured yet.



57
58
59
# File 'lib/y_petri/net/data_set.rb', line 57

def type
  @type
end

Class Method Details

.__new__Object



34
# File 'lib/y_petri/net/data_set.rb', line 34

alias __new__ new

.new(type: nil) ⇒ Object



36
37
38
39
40
41
42
43
44
# File 'lib/y_petri/net/data_set.rb', line 36

def new type: nil
  __new__ do |hsh, missing|
    case missing
    when Float then nil
    else hsh[ missing.to_f ] end
  end.tap { |inst|
    inst.instance_variable_set :@type, type
  }
end

Instance Method Details

#Assignment(array) ⇒ Object

Expects an array of assignment feature identifiers. Returns a subset of this dataset with only the specified assignment features retained.



326
327
328
# File 'lib/y_petri/net/data_set.rb', line 326

def Assignment array
  reduce_features assignment: array
end

#assignment(*ids) ⇒ Object

Expects an arbitrary number of assignment feature identifiers as arguments, and returns a subset of this dataset with only the specified assignment features retained. If no arguments are given, all the assignment features are assumed.



335
336
337
338
# File 'lib/y_petri/net/data_set.rb', line 335

def assignment *ids
  return reduce_features net.State.Features.assignment if args.empty?
  reduce_features assignment: ids
end

#ceiling(event, equal_ok = true) ⇒ Object

Returns the nearest event greater or equal to the supplied event-type argument. The second optional ordered argument, true by default, controls whether equality is accepted. If set to false, then the nearest greater event is sought.



87
88
89
90
# File 'lib/y_petri/net/data_set.rb', line 87

def ceiling( event, equal_ok=true )
  e = events.ascending_ceiling( event, equal_ok )
  e.nil? ? nil : e
end

#Delta(array, transitions: nil, **named_args) ⇒ Object

Expects an array of delta feature identifiers, optionally qualified by the :transitions named argument, defaulting to all the transitions in the net.



286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/y_petri/net/data_set.rb', line 286

def Delta array, transitions: nil, **named_args
  if named_args.has? :delta_time, syn!: :

#delta(*ordered_args, transitions: nil, **named_args) ⇒ Object

Expects an arbitrary number of ordered arguments identifying delta features, optionally qualified by the :transitions named argument, defaulting to all the transitions in the net.



307
308
309
310
311
312
# File 'lib/y_petri/net/data_set.rb', line 307

def delta *ordered_args, transitions: nil, **named_args
  return Delta( ordered_args, transitions: transitions, **named_args ) unless
    ordered_args.empty?
  return Delta( net.places, **named_args ) if transitions.nil?
  Delta( net.places, transitions: transitions, **named_args )
end

#delta_timed(*ordered_args, **named_args) ⇒ Object



315
316
317
# File 'lib/y_petri/net/data_set.rb', line 315

def delta_timed *ordered_args, **named_args
  delta *ordered_args, transitions: net.T_transitions, **named_args
end

#delta_timeless(*ordered_args, **named_args) ⇒ Object



319
320
321
# File 'lib/y_petri/net/data_set.rb', line 319

def delta_timeless *ordered_args, **named_args
  delta *ordered_args, transitions: net.t_transitions, **named_args
end

#distance(other) ⇒ Object

Computes the distance to another dataset.



151
152
153
154
155
156
157
158
# File 'lib/y_petri/net/data_set.rb', line 151

def distance( other )
  sum_of_sq = events
    .map { |e| [ e, other.interpolate( e ) ] }
    .map { |rec1, rec2| rec1.euclidean_distance rec2 }
    .map { |dist| dist * dist }
    .reduce( :+ )
  sum_of_sq ** 0.5
end

#firing(*ids, **named_args) ⇒ Object

Expects an arbitrary number of firing feature identifiers and returns a subset of this dataset with only the specified firing features retained. Named arguments may include :delta_time, alias :Δt (for firing of timed transitions).



239
240
241
242
# File 'lib/y_petri/net/data_set.rb', line 239

def firing *ids, **named_args
  return Firing net.State.Features.firing, **named_args if ids.empty?
  Firing ids, **named_args
end

#Firing(array, **named_args) ⇒ Object

Expects an array of firing feature identifiers, and returns a subset of this dataset with only the specified firing features retained. Named arguments may include :delta_time, alias :Δt (for firing of timed transitions).



230
231
232
# File 'lib/y_petri/net/data_set.rb', line 230

def Firing array, **named_args
  reduce_features firing: array, **named_args
end

#floor(event, equal_ok = true) ⇒ Object

Returns the nearest event smaller or equal to the supplied event-type argument. The second optional ordered argument, true by default, controls whether equality is accepted. If set to false, then the nearest smaller event is sought.



77
78
79
80
# File 'lib/y_petri/net/data_set.rb', line 77

def floor( event, equal_ok=true )
  e = events.ascending_floor( event, equal_ok )
  e.nil? ? nil : e
end

#Flux(array) ⇒ Object

Expects an array of flux feature identifiers, and returns a subset of this dataset with only the specified flux features retained.



247
248
249
# File 'lib/y_petri/net/data_set.rb', line 247

def Flux array
  reduce_features flux: array
end

#flux(*ids) ⇒ Object

Expects an arbitrary number of flux feature identifiers, and returns a subset of this dataset, with only the specified flux features retained. If no aruments are given, full set of flux features is assumed.



255
256
257
258
# File 'lib/y_petri/net/data_set.rb', line 255

def flux *ids
  return Flux net.State.Features.flux if ids.empty?
  Flux ids
end

#Gradient(array, transitions: nil) ⇒ Object

Expects an array of gradient feature identifiers, optionally qualified by the :transitions named argument, defaulting to all T transitions in the net.



264
265
266
267
268
269
270
# File 'lib/y_petri/net/data_set.rb', line 264

def Gradient array, transitions: nil
  if transitions.nil? then
    reduce_features gradient: array
  else
    reduce_features gradient: [ *array, transitions: transitions ]
  end
end

#gradient(*ids, transitions: nil) ⇒ Object

Returns a subset of this dataset with only the specified gradient features identified by the arguments retained. If no arguments are given, all the gradient features from the receiver dataset are selected.



276
277
278
279
280
# File 'lib/y_petri/net/data_set.rb', line 276

def gradient *ids, transitions: nil
  return Gradient net.State.Features.gradient, transitions: transitions if
    ids.empty?
  Gradient ids, transitions: transitions
end

#inspectObject

Inspect string of the instance.



439
440
441
# File 'lib/y_petri/net/data_set.rb', line 439

def inspect
  to_s
end

#interpolate(event) ⇒ Object Also known as: at

Interpolates the recording at the given point (event). Return value is the Record class instance.



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/y_petri/net/data_set.rb', line 114

def interpolate( event )
  begin
    record( event )
  rescue KeyError => msg
    timed? or raise TypeError, "Event #{event} not recorded! (%s)" %
      "simulation type: #{type.nil? ? 'nil' : type}"
    # (Remark: #floor, #ceiling supported by timed datasets only)
    floor = floor( event )
    fail "Event #{event} has no floor!" if floor.nil?
    fl = Matrix.column_vector record( floor )
    ceiling = ceiling( event )
    fail "Event #{event} has no ceiling!" if ceiling.nil?
    ce = Matrix.column_vector record( ceiling )
    rslt = fl + ( ce - fl ) / ( ceiling - floor ) * ( event - floor )
    features.load( rslt.column_to_a )
  end
end

#Marking(array) ⇒ Object

Expects an array of marking feature identifiers, and returns a subset of this dataset with only the specified marking features retained.



212
213
214
# File 'lib/y_petri/net/data_set.rb', line 212

def Marking array
  reduce_features marking: array
end

#marking(*ids) ⇒ Object

Expects an arbitrary number of marking feature identifiers, and returns a subset of this dataset with only the specified marking features retained. If no arguments are given, all the marking features are assumed.



220
221
222
223
# File 'lib/y_petri/net/data_set.rb', line 220

def marking *ids
  return Marking net.State.Features.marking if ids.empty?
  Marking ids
end

#plot(nodes = nil, except: [], **named_args) ⇒ Object

Plots the dataset. Takes several optional arguments: The list of nodes can be supplied as optional first ordered argument, which are then converted into features using Net::State::Features.infer_from_nodes method. Similarly, the features to exclude can be specifies as a list of nodes (or a feature-specifying hash) supplied under except: keyword. Otherwise, feature specification can be passed to the method as named arguments. If no feature specification is explicitly provided, it is assumed that all the features of this dataset are meant to be plotted.



357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
# File 'lib/y_petri/net/data_set.rb', line 357

def plot( nodes=nil, except: [], **named_args )
  nn = named_args
  time = nn.may_have :time, syn!: :time_range
  events = events()
  # Figure out features.
  ff = if nodes.nil? then
         nn_ff = nn.slice [ :marking, :flux, :firing,
                            :gradient, :delta, :assignment ]
         nn_ff.empty? ? features : net.State.Features( nn_ff )
       else
         net.State.Features.infer_from_nodes( nodes )
       end
  # Figure out the features not to plot ("except" features).
  xff = case except
        when Array then net.State.Features.infer_from_nodes( except )
        when Hash then net.State.Features( except )
        else
          fail TypeError, "Wrong type of :except argument: #{except.class}"
        end
  # Subtract the "except" features from features to plot.
  ff -= xff
  # Convert the feature set into a set of data arrays.
  data_arrays = series( ff )
  # Figure out the x axis range for plotting.
  x_range = if nn.has? :time then
              if time.is_a? Range then
                "[#{time.begin}:#{time.end}]"
              else
                "[-0:#{SY::Time.magnitude( time ).amount rescue time}]"
              end
            else
              from = events.first || 0
              to = if events.last and events.last > from then events.last
                   else events.first + 1 end
              "[#{from}:#{to}]"
            end
  # Invoke Gnuplot.
  Gnuplot.open do |gp|
    Gnuplot::Plot.new gp do |plot|
      plot.xrange x_range
      if nn.has? :yrange, syn!: :y_range then
        if nn[:yrange].is_a? Range then
          plot.yrange "[#{nn[:yrange].begin}:#{nn[:yrange].end}]"
        else fail TypeError, "Argument :yrange is not a range!" end
      end
      plot.title nn[:title] || "#{net} plot"
      plot.ylabel nn[:ylabel] || "Values"
      plot.xlabel nn[:xlabel] || "Time [s]"
      ff.labels.zip( data_arrays ).each do |label, array|
        # Replace NaN and Infinity with 0.0 and warn about it.
        nan, inf = 0, 0
        array = array.map { |v|
          if v.to_f.infinite? then inf += 1; 0.0
          elsif v.to_f.nan? then nan += 1; 0.0
          else v end
        }
        # Warn.
        nan = nan > 0 ? "#{nan} NaN values" : nil
        inf = inf > 0 ? "#{inf} infinite values" : nil
        msg = "Warning: column #{label} contains %s plotted as 0!"
        warn msg % [ nan, inf ].compact.join( ' and ' ) if nan or inf
        # Finally, plot.
        plot.data << Gnuplot::DataSet.new( [ events, array ] ) { |set|
          set.with = "linespoints"
          set.title = label
        }
      end
    end
  end
end

Pretty print the dataset. Takes :precision and :distance named arguments, that control the shape of the printed table.



446
447
448
449
450
451
452
453
# File 'lib/y_petri/net/data_set.rb', line 446

def print precision: 4, distance: precision + 4
  features.labels.print_as_line precision: precision, distance: distance
  puts '-' * features.size * distance
  records.each { |record|
    record.print_as_line precision: precision, distance: distance
  }
  return nil
end

#reconstruct(at: (fail "No event given!"), **settings) ⇒ Object

Recreates the simulation at a given event label.



100
101
102
103
104
105
106
107
108
109
# File 'lib/y_petri/net/data_set.rb', line 100

def reconstruct at: (fail "No event given!"), **settings
  # settings may include marking clamps, marking, inital marking...
  record = interpolate( at )
  settings = settings().merge settings if settings()
  if timed? then
    record.reconstruct time: at, **settings
  else
    record.reconstruct **settings
  end
end

#record(event) ⇒ Object

Returns the Record instance corresponding to the given recorded event.



68
69
70
# File 'lib/y_petri/net/data_set.rb', line 68

def record( event )
  features.load( fetch event )
end

#recordsObject

Revives records from values.



94
95
96
# File 'lib/y_petri/net/data_set.rb', line 94

def records
  values.map { |value| features.Record.new( value ) }
end

#reduce_features(array = nil, **named_args) ⇒ Object

Expects a hash of features (:marking (alias :state) of places, :firing of tS transitions, :delta of places and/or transitions, :assignment of A transitions) and returns the corresponding mapping of the recording.



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/y_petri/net/data_set.rb', line 171

def reduce_features array=nil, **named_args
  delta_time_given = named_args.has? :delta_time, syn!: :

#resample(**settings) ⇒ Object

Resamples the recording.



135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/y_petri/net/data_set.rb', line 135

def resample **settings
  time_range = settings.may_have( :time_range, syn!: :time ) ||
    events.first .. events.last
  sampling = settings.must_have :sampling
  t0, target_time = time_range.begin, time_range.end
  t = t0
  o = self.class.new type: type
  loop do
    o.update t => interpolate( t )
    t += sampling
    return o if t > target_time
  end
end

#series(array = nil) ⇒ Object

Returns the data series for the specified features.



162
163
164
165
# File 'lib/y_petri/net/data_set.rb', line 162

def series array=nil
  return records.transpose if array.nil?
  reduce_features( net.State.Features array ).series
end

#timed?Boolean

Type of the dataset.



62
63
64
# File 'lib/y_petri/net/data_set.rb', line 62

def timed?
  type == :timed
end

#to_csvObject

Outputs the current recording in CSV format.



342
343
344
345
346
# File 'lib/y_petri/net/data_set.rb', line 342

def to_csv
  require 'csv'
  [ ":event", *features.labels.map( &:to_s ) ].join( ',' ) + "\n" +
    map { |lbl, rec| [ lbl, *rec ].join ',' }.join( "\n" )
end

#to_sObject

Returns a string briefly discribing the dataset.



430
431
432
433
434
435
# File 'lib/y_petri/net/data_set.rb', line 430

def to_s
  "#<DataSet: " +
    "#{keys.size} records, " +
    "features: #{features}" +
    ">"
end