class YPetri::Net::DataSet

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.

Attributes

settings[R]
type[R]

Public Class Methods

new(type: nil) click to toggle source
# File lib/y_petri/net/data_set.rb, line 56
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
Also aliased as: __new__

Private Class Methods

__new__(type: nil)
Alias for: new

Public Instance Methods

Assignment(array) click to toggle source

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

# File lib/y_petri/net/data_set.rb, line 346
def Assignment array
  reduce_features assignment: array
end
Delta(array, transitions: nil, **named_args) click to toggle source

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

# File lib/y_petri/net/data_set.rb, line 306
def Delta array, transitions: nil, **named_args
  if named_args.has? :delta_time, syn!: :Δt then
    Δt = named_args.delete( :delta_time )
    if transitions.nil? then
      reduce_features delta: array, Δt: Δt
    else
      reduce_features delta: [ *array, transitions: transitions ], Δt: Δt
    end
  else
    if transitions.nil? then
      reduce_features delta: array
    else
      reduce_features delta: [ *array, transitions: transitions ]
    end
  end
end
Firing(array, **named_args) click to toggle source

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).

# File lib/y_petri/net/data_set.rb, line 250
def Firing array, **named_args
  reduce_features firing: array, **named_args
end
Flux(array) click to toggle source

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

# File lib/y_petri/net/data_set.rb, line 267
def Flux array
  reduce_features flux: array
end
Gradient(array, transitions: nil) click to toggle source

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

# File lib/y_petri/net/data_set.rb, line 284
def Gradient array, transitions: nil
  if transitions.nil? then
    reduce_features gradient: array
  else
    reduce_features gradient: [ *array, transitions: transitions ]
  end
end
Marking(array) click to toggle source

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

# File lib/y_petri/net/data_set.rb, line 232
def Marking array
  reduce_features marking: array
end
assignment(*ids) click to toggle source

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.

# File lib/y_petri/net/data_set.rb, line 355
def assignment *ids
  return reduce_features net.State.Features.assignment if args.empty?
  reduce_features assignment: ids
end
at( event )
Alias for: interpolate
ceiling( event, equal_ok=true ) click to toggle source

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.

# File lib/y_petri/net/data_set.rb, line 107
def ceiling( event, equal_ok=true )
  e = events.ascending_ceiling( event, equal_ok )
  e.nil? ? nil : e
end
delta(*ordered_args, transitions: nil, **named_args) click to toggle source

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.

# File lib/y_petri/net/data_set.rb, line 327
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) click to toggle source
# File lib/y_petri/net/data_set.rb, line 335
def delta_timed *ordered_args, **named_args
  delta *ordered_args, transitions: net.T_transitions, **named_args
end
delta_timeless(*ordered_args, **named_args) click to toggle source
# File lib/y_petri/net/data_set.rb, line 339
def delta_timeless *ordered_args, **named_args
  delta *ordered_args, transitions: net.t_transitions, **named_args
end
distance( other ) click to toggle source

Computes the distance to another dataset.

# File lib/y_petri/net/data_set.rb, line 171
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) click to toggle source

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).

# File lib/y_petri/net/data_set.rb, line 259
def firing *ids, **named_args
  return Firing net.State.Features.firing, **named_args if ids.empty?
  Firing ids, **named_args
end
floor( event, equal_ok=true ) click to toggle source

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.

# File lib/y_petri/net/data_set.rb, line 97
def floor( event, equal_ok=true )
  e = events.ascending_floor( event, equal_ok )
  e.nil? ? nil : e
end
flux(*ids) click to toggle source

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.

# File lib/y_petri/net/data_set.rb, line 275
def flux *ids
  return Flux net.State.Features.flux if ids.empty?
  Flux ids
end
gradient(*ids, transitions: nil) click to toggle source

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.

# File lib/y_petri/net/data_set.rb, line 296
def gradient *ids, transitions: nil
  return Gradient net.State.Features.gradient, transitions: transitions if
    ids.empty?
  Gradient ids, transitions: transitions
end
inspect() click to toggle source

Inspect string of the instance.

# File lib/y_petri/net/data_set.rb, line 468
def inspect
  to_s
end
interpolate( event ) click to toggle source

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

# File lib/y_petri/net/data_set.rb, line 134
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
Also aliased as: at
marking(*ids) click to toggle source

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.

# File lib/y_petri/net/data_set.rb, line 240
def marking *ids
  return Marking net.State.Features.marking if ids.empty?
  Marking ids
end
plot( nodes=nil, except: [], **named_args ) click to toggle source

Plots the dataset using Ruby Gnuplot gem. Takes several optional arguments: The list of nodes to plot can be supplied as optional first ordered argument. If supplied, the nodes are converted into features using Net::State::Features.infer_from_nodes method. Similarly, the features to exclude can be specified as a list of nodes supplied under except: parameter. Under except: parameter, it is also possible to supply a feature-specifying hash. Otherwise, feature specification can be passed to the method via named arguments. By default, it is assumed that the caller means to plot all the features of this dataset.

Named arguments admit Gnuplot keywords that control the plot. These parameters include title:, xlabel: and ylabel:.

# File lib/y_petri/net/data_set.rb, line 385
def plot( nodes=nil, except: [], **named_args )
  nn = named_args
  time = nn.may_have :time, syn!: :time_range
  events = events()
  # Figure out the features to plot.
  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 to exclude from plotting
  # ("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
print(precision: 4, distance: precision + 4) click to toggle source

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

reconstruct(at: (fail "No event given!"), **settings) click to toggle source

Recreates the simulation at a given event label.

# File lib/y_petri/net/data_set.rb, line 120
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 ) click to toggle source

Returns the Record instance corresponding to the given recorded event.

# File lib/y_petri/net/data_set.rb, line 88
def record( event )
  features.load( fetch event )
end
records() click to toggle source

Revives records from values.

# File lib/y_petri/net/data_set.rb, line 114
def records
  values.map { |value| features.Record.new( value ) }
end
reduce_features(array=nil, **named_args) click to toggle source

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.

# File lib/y_petri/net/data_set.rb, line 191
def reduce_features array=nil, **named_args
  delta_time_given = named_args.has? :delta_time, syn!: :Δt
  Δt = named_args.delete :delta_time
  ff = net.State.Features[ *array, **named_args ] # reduced feature set
  absent = ff - features()         # features absent from the current set
  present = ff - absent            # features present in the current set
  timedness = true if absent.any? { |f| f.timed? rescue false }
  fail ArgumentError, "Reconstruction of timed features requires Δt to be" +
    "supplied!" unless delta_time_given if timedness
  present_ii =
    present.each_with_object( {} ) { |f, | [f] = features().index f }
  ds = ff.DataSet.new type: type
  if absent.empty? then # no reconstruction
    ( events >> records ).each_with_object ds do |(event, record), dataset|
      line = record.values_at *ff.map( &present_ii.method( :[] ) )
      dataset.update event => ff.load( line )
    end
  else
    ( events >> records ).each_with_object ds do |(event, record), dataset|
      reconstructed_sim = reconstruct at: event
      line = if timedness then
               ff.map { |f|
                 i = present_ii[ f ]
                 break record[ i ] if i
                 f.extract_from( reconstructed_sim, Δt: Δt )
               }
             else
               ff.map { |f|
                 i = present_ii[ f ]
                 break record[ i ] if i
                 f.extract_from( reconstructed_sim, Δt: Δt )
               }
             end
      dataset.update event => ff.load( line )
    end
  end
end
resample(**settings) click to toggle source

Resamples the recording.

# File lib/y_petri/net/data_set.rb, line 155
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) click to toggle source

Returns the data series for the specified features.

# File lib/y_petri/net/data_set.rb, line 182
def series array=nil
  return records.transpose if array.nil?
  reduce_features( net.State.Features array ).series
end
timed?() click to toggle source

Type of the dataset.

# File lib/y_petri/net/data_set.rb, line 82
def timed?
  type == :timed
end
to_csv() click to toggle source

Outputs the current recording in CSV format.

# File lib/y_petri/net/data_set.rb, line 362
def to_csv
  require 'csv'
  [ ":event", *features.labels.map( &:to_s ) ].join( ',' ) + "\n" +
    map { |lbl, rec| [ lbl, *rec ].join ',' }.join( "\n" )
end
to_s() click to toggle source

Returns a string briefly discribing the dataset.

# File lib/y_petri/net/data_set.rb, line 459
def to_s
  "#<DataSet: " +
    "#{keys.size} records, " +
    "features: #{features}" +
    ">"
end