# encoding: utf-8 require_relative 'transition/arcs' require_relative 'transition/cocking' require_relative 'transition/construction_convenience' require_relative 'transition/types' require_relative 'transition/usable_without_world' # 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 ★ NameMagic # ★ means include ★ YPetri::World::Dependency ★ UsableWithoutWorld ★ Arcs ★ Cocking ★ ConstructionConvenience ★ Types class << self ★ YPetri::World::Dependency private :new end TYPES = { T: "timed", t: "timeless", S: "stoichiometric", s: "non-stoichiometric", A: "assignment", a: "non-assignment", TS: "timed stoichiometric", tS: "timeless stoichiometric", Ts: "timed nonstoichiometric", ts: "timeless nonstoichiometric" } delegate :world, to: "self.class" # Transition class represents many different kinds of Petri net transitions. # It makes the constructor syntax a bit more polymorphic. The type of the # transition to construct is mostly inferred from the constructor arguments. # # Mandatorily, the constructor will always need a way to determine the domain # (upstream arcs) and codomain (downstream arcs) of the transition. Also, the # constructor must have a way to determine the transition's action. This is # best explained by examples -- let us have 3 places A, B, C, for whe we will # create different kinds of transitions: # # # ==== TS (timed stoichiometric) # # Rate closure and stoichiometry has to be supplied. Rate closure arity should # correspond to the domain size. Return arity should be 1 (to be multiplied by # the stoichiometry vector, as in all other stoichiometric transitions). # # Transition.new stoichiometry: { A: -1, B: 1 }, # rate: -> a { a * 0.5 } # # # ==== Ts (timed nonstoichiometric) # # Rate closure has to be supplied, whose arity should match the domain, and # output arity codomain. # # ==== tS (timeless stoichiometric) # # Stoichiometry has to be supplied, action closure is optional. If supplied, # its return arity should be 1 (to be multiplied by the stoichiometry vector). # # ==== ts transitions (timeless nonstoichiometric) # # Action closure is expected with return arity equal to the codomain size: # # Transition.new upstream_arcs: [A, C], downstream_arcs: [A, B], # action_closure: proc { |m, x| # if x > 0 then [-(m / 2), (m / 2)] # else [1, 0] end # } # def initialize *args, &block check_in_arguments *args, &block # the big job extend( if timed? then Type_T elsif assignment_action? then Type_A else Type_t end ) inform_upstream_places # that they have been connected inform_downstream_places # that they have been connected uncock # initialize in the uncocked state 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 # 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 # 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 "#" % "#{'%s ' % name if name}(#{type})#{' id:%s' % object_id unless name}" end def place id super rescue Place().instance( id ) end def transition id super rescue Transition().instance( id ) end end # class YPetri::Transition