# encoding: utf-8 require_relative 'transition/arcs' require_relative 'transition/cocking' require_relative 'transition/init' require_relative 'transition/timed' require_relative 'transition/ordinary_timeless' require_relative 'transition/assignment' # Transitions -- little boxes in Petri net drawings -- represent atomic # operations on the Petri net's marking. # # === Domain and codomin # # Each transition has a _domain_ (upstream places) and _codomain_ (downstream # places). Upstream places are those, whose marking affects the transition. # Downstream places are those affected by the transition. # # === Action and action vector # # Every transition _action_ -- the operation it represents. The action of # _non-stoichiometric_ transitions is directly specified by its _action_ # _closure_ (whose output arity should match the codomain size.) For # _stoichiometric_ transitions, the action closure result must be multiplied # by the transition's _stoichiometry_ _vector_. _Timed_ _transitions_ have # _rate_ _closure_. Their action can be obtained by multiplying their rate # by Δtime. # # === Rate # # In YPetri, marking is always considered a discrete number of _tokens_ (as # C. A. Petri has handed it down to us). Usefulness of floating point numbers # in representing larger amounts of tokens is acknowledged, but seen as a # pragmatic measure, an implementation detail. There is no class distinction # between discrete vs. continuous places / transitions. Often we see continuous # transitions with their _flux_ (flow rate) ditinguished from discrete # stochastic transitions with their _propensity_ (likelihood of firing in a # time unit). In YPetri, flux and propensity are unified under a single term # _rate_, and the choice between discrete and stochastic computation is a # concern of the simulation, not of the object model. # # === Basic transition types # # There are 4 basic transition types in YPetri: # # * *TS* – timed stoichiometric # * *tS* – timeless stoichiometric # * *Ts* – timed nonstoichiometric # * *ts* – timeless nonstoichiometric # # They arise by combining 2 qualities: # # 1. *Timedness*: _timed_ (*T*) / _timeless_ (*t*) # 2. *Stoichiometricity*: _stoichiometric_ (*S*) / _nonstoichiometric_ (*s*) # # ==== Timedness # # * Timed transitions have _rate_ _closure_, whose result is to be multiplied # by +Δtime+. # * Timeless transitions have _action_ _closure_, whose result does not need # to be multiplied by time. # # Summary: Having vs. not having rate distinguishes the need to multiply the # closure result by Δ time. # # ==== Stoichiometricity # # * *TS* transitions -- rate vector = rate * stoichiometry vector # * *tS* transitions -- action vector = action * stoichiometry vector # * *Ts* transitions -- rate vector = rate closure result # * *ts* transitions -- action vector = action closure result # # Summary: stoichiometricity distinguishes the need to multiply the rate/action # closure result by stoichiometry. # # === Assignment action # # _Assignment_ _transitions_ (_*A*_ _transitions_) are special transitions, that # _replace_ the codomain marking rather than modifying it -- they _assign_ new # marking to their codomain, like we are used to from spreadsheets. Technically, # this behavior is easily achievable with normal *ts* transitions, so the # existence of separate *A* transitions is just a convenience, not a new type of # a transition in the mathematical sense. # # ==== _Functional_ / _functionless_ transitions # # Other Petri net implementation often distinguies between "ordinary" (vanilla # as per C. A. Petri) and _functional_ transitions, whose operation is governed # by a function. In YPetri, transitions are generally _functional_, but there # remains a possibility of creating vanilla (_functionless_) transitions by not # specifying any rate / action, while specifying the stoichiometry. Action # closure as per C. A. Petri is automatically constructed for these. # class YPetri::Transition include NameMagic include YPetri::World::Dependency class << self include YPetri::World::Dependency end delegate :world, to: "self.class" BASIC_TRANSITION_TYPES = { TS: "timed stoichiometric", tS: "timeless stoichiometric", Ts: "timed nonstoichiometric", ts: "timeless nonstoichiometric" } def TS?; type == :TS end def Ts?; type == :Ts end def tS?; type == :tS end def ts?; type == :ts end # Domain, or 'upstream arcs', is a collection of places, whose marking # directly affects the transition's action. # attr_reader :domain alias :domain_arcs :domain alias :domain_places :domain alias :upstream :domain alias :upstream_arcs :domain alias :upstream_places :domain # Codomain, 'downstream arcs', or 'action arcs', is a collection of places, # whose marking is directly changed by this transition's firing. # attr_reader :codomain alias :codomain_arcs :codomain alias :codomain_places :codomain alias :downstream :codomain alias :downstream_arcs :codomain alias :downstream_places :codomain alias :action_arcs :codomain # Is the transition stoichiometric? # def stoichiometric?; @stoichiometric end alias :S? :stoichiometric? # Is the transition nonstoichiometric? (Opposite of #stoichiometric?) # def nonstoichiometric? not stoichiometric? end alias :s? :nonstoichiometric? # Stoichiometry (implies that the transition is stoichiometric). # attr_reader :stoichiometry # Stoichiometry as a hash of pairs: # { codomain_place_instance => stoichiometric_coefficient } # def stoichio Hash[ codomain.zip( @stoichiometry ) ] end # Stoichiometry as a hash of pairs: # { codomain_place_name_symbol => stoichiometric_coefficient } # def s stoichio.with_keys { |k| k.name || k.object_id } end # In YPetri, _rate_ is a unifying term for both _flux_ and _propensity_, # both of which are treated as aliases of _rate_. The decision between # discrete and continuous computation is a concern of the simulation. # Rate closure arity should correspond to the transition's domain. # attr_reader :rate_closure alias :rate :rate_closure alias :flux_closure :rate_closure alias :flux :rate_closure alias :propensity_closure :rate_closure alias :propensity :rate_closure # For rateless transition, action closure must be present. Action closure # input arguments must correspond to the domain places, and for timed # transitions, the first argument of the action closure must be Δtime. # attr_reader :action_closure alias :action :action_closure # Does the transition's action depend on delta time? # def timed? @timed end alias T? timed? # Is the transition timeless? (Opposite of #timed?) # def timeless? not timed? end alias t? timeless? # Is the transition functional? # Explanation: If rate or action closure is supplied, a transition is always # considered 'functional'. Otherwise, it is considered not 'functional'. # Note that even transitions that are not functional still have standard # action acc. to Petri's definition. Also note that a timed transition is # necessarily functional. # def functional? @functional end # Opposite of #functional? # def functionless? not functional? end # Reports the transition's membership in one of the 4 basic types: # # 1. TS .... timed stoichiometric # 2. tS .... timeless stoichiometric # 3. Ts .... timed nonstoichiometric # 4. ts .... timeless nonstoichiometric # # plus the fifth type # # 5. A .... assignment transitions # def type return :A if assignment_action? timed? ? ( stoichiometric? ? :TS : :Ts ) : ( stoichiometric? ? :tS : :ts ) end # Is it an assignment transition? (Transitions with 'assignment action' # completely replace their codomain's marking.) # def assignment_action?; @assignment_action end alias :assignment? :assignment_action? alias :A? :assignment_action? # Is it a non-assignment transition? (Opposite of +#A?+) # def a?; ! assignment_action? end # Zero action. # def zero_action codomain.map { 0 } end # def lock # # LATER # end # alias :disable! :force_disabled # def unlock # # LATER # end # alias :undisable! :remove_force_disabled # def force_enabled!( boolean ) # # true - the transition is always regarded as enabled # # false - the status is removed # # LATER # end # def clamp # # LATER # end # def remove_clamp # # LATER # end # def reset! # uncock # remove_force_disabled # remove_force_enabled # remove_clamp # return self # end # Inspect string for a transition. # def inspect to_s end # Conversion to a string. # def to_s "#" % "#{name.nil? ? '' : '%s ' % name }(#{type}%s)%s" % [ "#{assignment_action? ? ' Assign.' : ''}", "#{name.nil? ? ' id:%s' % object_id : ''}" ] end def place id super rescue Place().instance( id ) end def transition id super rescue Transition().instance( id ) end end # class YPetri::Transition