lib/y_petri/net/state.rb in y_petri-2.1.3 vs lib/y_petri/net/state.rb in y_petri-2.1.6

- old
+ new

@@ -1,121 +1,129 @@ # encoding: utf-8 -class YPetri::Net - # Petri net state (marking of all its places). - # - class State < Array - require_relative 'state/feature' - require_relative 'state/features' +# An array whose elements correspond to the full marking of the net's places. +# +class YPetri::Net::State < Array + require_relative 'state/feature' + require_relative 'state/features' - class << self - # Customization of the parametrize method for the State class: Its - # dependents Feature and Features (ie. feature set) are also parametrized. - # - def parametrize net: (fail ArgumentError, "No owning net!") - Class.new( self ).tap do |subclass| - subclass.define_singleton_method :net do net end - subclass.param_class( { Feature: Feature, - Features: Features }, - with: { State: subclass } ) - end + class << self + # Customization of the parametrize method for the State class: Its + # dependents Feature and Features (ie. feature set) are also parametrized. + # + def parametrize net: ( fail ArgumentError, "No owning net!" ) + Class.new( self ).tap do |ç| + ç.define_singleton_method :net do net end + ç.param_class( { Feature: Feature, + Features: Features }, + with: { State: ç } ) end + end - delegate :Marking, - :Firing, - :Gradient, - :Flux, - :Delta, - to: "Feature()" + delegate :Marking, + :Firing, + :Gradient, + :Flux, + :Delta, + to: "Feature()" - alias __new__ new - - # Revives a state from a record and a given set of marking clamps. - # - def new record, marking_clamps: {} - cc = marking_clamps.with_keys { |k| net.place k }.with_values! do |v| - case v - when YPetri::Place then v.marking - when ~:call then v.call - else v end + # Returns the feature identified by the argument. + # + def feature *id + fail ArgumentError, "No feature identifier!" if id.empty? + case id.first + when Feature() then id.first + when Feature then id.first.class.new( id.first ) + else + msg = "Malformed feature identifier!" + fail ArgumentError, msg unless id.size == 1 and id.first.is_a? Hash + ꜧ = id.first + fail ArgumentError, msg unless ꜧ.size == 1 + key, val = ꜧ.keys.first, ꜧ.values.first + recognized = :marking, :firing, :gradient, :flux, :delta + msg = "Unrecognized feature: #{key}" + fail ArgumentError, msg unless recognized.include? key + # And now, with everything clean... + case key + when :marking then Marking( val ) + when :firing then Firing( val ) + when :flux then Flux( val ) + when :gradient then Gradient( *val ) + when :delta then Delta( *val ) end - - record = features( marking: net.pp - cc.keys ).load( record ) - - __new__ net.pp.map do |p| - begin; cc.fetch p; rescue IndexError - record.fetch Marking().of( p ) - end - end end + end - # Returns the feature identified by the argument. - # - def feature id - case id - when Feature() then id - when Feature then id.class.new( id ) - else - features( id ).tap do |ff| - ff.size == 1 or fail ArgumentError, "Argument #{id} must identify " + - "exactly 1 feature!" - end.first - end + # If the argument is an array of features, or another Features instance, + # a feature set based on this array is returned. But the real purpose of + # this method is to allow hash-type argument, with keys +:marking+, + # +:firing+, +:gradient+, +:flux+ and +:delta+, specifying the respective + # features. For +:marking+, an array of places (or Marking features) is + # expected. For +:firing+ and +:flux+, an array of transitions (or Firing + # / Flux features) is expected. For +:gradient+ and +:delta+, a hash value + # is expected, containing keys +:places+ and +:transitions+, specifying + # for which place set / transition set should gradient / delta features + # be constructed. More in detail, values supplied under keys +:marking+, + # +:firing+, +:gradient+, +:flux+ and +:delta+ are delegated to + # +Features.marking+, +Features.firing+, +Features.gradient+ and + # +Features.flux+ methods, and their results are joined into a single + # feature set. + # + def features arg + case arg + when Features(), Array then Features().new( arg ) + else # the real job of the method + marking = arg[:marking] || [] + firing = arg[:firing] || [] # array of tS transitions + gradient = arg[:gradient] || [ [], transitions: [] ] + flux = arg[:flux] || [] # array of TS transitions + delta = arg[:delta] || [ [], transitions: [] ] + [ Features().marking( marking ), + Features().firing( firing ), + Features().gradient( *gradient ), + Features().flux( flux ), + Features().delta( *delta ) ].reduce :+ end + end - # If the argument is an array of features, or another Features instance, - # a feature set based on this array is returned. But the real purpose of - # this method is to allow hash-type argument, with keys +:marking+, - # +:firing+, +:gradient+, +:flux+ and +:delta+, specifying the respective - # features. For +:marking+, an array of places (or Marking features) is - # expected. For +:firing+ and +:flux+, an array of transitions (or Firing - # / Flux features) is expected. For +:gradient+ and +:delta+, a hash value - # is expected, containing keys +:places+ and +:transitions+, specifying - # for which place set / transition set should gradient / delta features - # be constructed. More in detail, values supplied under keys +:marking+, - # +:firing+, +:gradient+, +:flux+ and +:delta+ are delegated to - # +Features.marking+, +Features.firing+, +Features.gradient+ and - # +Features.flux+ methods, and their results are joined into a single - # feature set. - # - def features arg - case arg - when Features(), Array then Features().new( arg ) - else # the real job of the method - marking = arg[:marking] || [] - firing = arg[:firing] || [] # array of tS transitions - gradient = arg[:gradient] || [ [], transitions: [] ] - flux = arg[:flux] || [] # array of TS transitions - delta = arg[:delta] || [ [], transitions: [] ] - [ Features().marking( marking ), - Features().firing( firing ), - Features().gradient( *gradient ), - Features().flux( flux ), - Features().delta( *delta ) ].reduce :+ - end - end + delegate :marking, :firing, :gradient, :flux, :delta, to: "Features()" + end - delegate :marking, :firing, :gradient, :flux, :delta, to: "Features()" - end + # For non-parametrized vesion of the class, the class instance variables + # hold the non-parametrized dependent classes. + # + @Feature, @Features = Feature, Features - # For non-parametrized vesion of the class, the class instance variables - # hold the non-parametrized dependent classes. - # - @Feature, @Features = Feature, Features + delegate :net, + :Feature, + :Features, + :features, + :marking, :firing, :gradient, :flux, :delta, + to: "self.class" - delegate :net, - :Feature, - :Features, - :features, - :marking, :firing, :gradient, :flux, :delta, - to: "self.class" + # Given a set of clamped places, this method outputs a Record instance + # containing the marking of the free places (complementary to the supplied + # set of clamped places). I no set of clamped places is supplied, it is + # considered empty. + # + def to_record clamped_places=[] + free_places = case clamped_places + when Hash then to_record( clamped_places.keys ) + else + free_places = places - places( clamped_places ) + end + features( marking: free_places ).Record.load markings( free_places ) + end - # Reconstructs a simulation from the current state instance, given marking - # clamps and other simulation settings. - # - def reconstruct marking_clamps: {}, **settings - net.simulation marking: to_hash, - marking_clamps: marking_clamps, - **settings - end - end # class State -end # YPetri::Net + # Marking of a single given place in this state. + # + def marking place_id + self[ places.index place( place_id ) ] + end + + # Returns an array of markings of particular places in this state.. + # + def markings place_ids=nil + return markings( places ) if place_ids.nil? + place_ids.map &:marking + end +end # YPetri::Net::State