lib/y_petri/transition.rb in y_petri-2.0.7 vs lib/y_petri/transition.rb in y_petri-2.0.14.p1

- old
+ new

@@ -1,128 +1,128 @@ # -*- coding: utf-8 -*- require_relative 'dependency_injection' require_relative 'transition/arcs' require_relative 'transition/cocking' -require_relative 'transition/constructor_syntax' +require_relative 'transition/construction' +require_relative 'transition/timed' +require_relative 'transition/ordinary_timeless' +require_relative 'transition/assignment' -# A Petri net transition. There are 6 basic types of YPetri transitions: -# -# * <b>ts</b> – timeless nonstoichiometric -# * <b>tS</b> – timeless stoichiometric -# * <b>Tsr</b> – timed rateless nonstoichiometric -# * <b>TSr</b> – timed rateless stoichiometric -# * <b>sR</b> – nonstoichiometric with rate -# * <b>SR</b> – stoichiometric with rate -# -# These 6 kinds of YPetri transitions correspond to the vertices of a cube, -# whose 3 dimensions are: +# A Petri net transition. Usually depicted as square boxes, transitions +# represent operations over the net's marking vector -- how the marking changes +# when the transition activates (_fires_). # -# - stoichiometric (S) / nonstoichiometric (s) -# - timed (T) / timeless (t) -# - having rate (R) / not having rate (r) -# -# I. For stoichiometric transitions: -# 1. Rate vector is computed as rate * stoichiometry vector, or -# 2. Δ vector is computed a action * stoichiometry vector. -# II. For non-stoichiometric transitions: -# 1. Rate vector is obtained as the rate closure result, or -# 2. action vector is obtained as the action closure result. -# -# Conclusion: stoichiometricity distinguishes *need to multiply the -# rate/action closure result by stoichiometry*. -# -# I. For transitions with rate, the closure result has to be -# multiplied by the time step duration (delta_t) to get action. -# II. For rateless transitions, the closure result is used as is. -# -# Conclusion: has_rate? distinguishes *need to multiply the closure -# result by delta time* -- differentiability of action by time. -# -# I. For timed transitions, action is time-dependent. Transitions with -# rate are thus always timed. In rateless transitions, timedness means -# that the action closure expects time step length (delta_t) as its first -# argument - its arity is thus codomain size + 1. -# II. For timeless transitions, action is time-independent. Timeless -# transitions are necessarily also rateless. Arity of the action closure -# is expected to match the domain size. -# -# Conclusion: Transitions with rate are always timed. In rateless -# transitions, timedness distinguishes the need to supply time step -# duration as the first argument to the action closure. -# -# Since transitions with rate are always timed, and vice-versa, timeless -# transitions cannot have rate, there are not 8, but only 6 permissible -# combinations -- 6 basic transition types listed above. -# # === Domain and codomin # -# Each transition has a domain, or 'upstream places': A collection of places +# Each transition has a _domain_ -- upstream places. Upstream places are those, # whose marking directly affects the transition's operation. Also, each -# transition has a codomain, or 'downstream places': A collection of places, +# transition has a _codomain_ -- downstream places. Downstream places are those, # whose marking is directly affected by the transition's operation. # # === Action and action vector # -# Regardless of the type, every transition has <em>action</em>: -# A prescription of how the transition changes the marking of its codomain -# when it fires. With respect to the transition's codomain, we can also -# talk about <em>action vector</em>. For non-stoichiometric transitions, -# the action vector is directly the output of the action closure or rate -# closure multiplied by Δtime, while for stoichiometric transitions, this -# needs to be additionaly multiplied by the transitions stoichiometric -# vector. Now we are finally equipped to talk about the exact meaning of -# 3 basic transition properties. +# Every transition has an _action_ -- the operation it represents, the of what +# happens to the marking of its codomain when it fires. With respect to the +# transition's codomain, we can talk about the _action vector_ -- Δ state of the +# codomain. For _non-stoichiometric_ transitions, this action vector is given +# as the output of the _action closure_, or (for transitions with rate) of _rate +# vector_ * Δ_time. For _stoichiometric_ transitions, this output needs to be +# additionally multiplied by the transition's _stoichiometry vector_. +# +# === Basic types of transitions +# +# We have already mentioned different types of transitions _stoichiometric_ and +# _non-stoichometric_, with or without rate... In total, there are 6 basic types +# of transitions in *YPetri*: # -# === Meaning of the 3 basic transition properties +# * *ts* – _timeless nonstoichiometric_ +# * *tS* – _timeless stoichiometric_ +# * *Tsr* – _timed rateless nonstoichiometric_ +# * *TSr* – _timed rateless stoichiometric_ +# * *sR* – _nonstoichiometric with rate_ +# * *SR* – _stoichiometric with rate_ # -# ==== Stoichiometric / non-stoichiometric -# * For stoichiometric transitions: -# [Rate vector] is computed as rate * stoichiometry vector, or -# [Δ vector] is computed a action * stoichiometry vector -# * For non-stoichiometric transitions: -# [Rate vector] is obtained as the rate closure result, or -# [action vector] is obtained as the action closure result. +# These 6 kinds of YPetri transitions correspond to the vertices of a cube, with +# the following 3 dimensions: # -# Conclusion: stoichiometricity distinguishes <b>need to multiply the -# rate/action closure result by stoichiometry</b>. +# - *Stoichiometricity*: _stoichiometric_ (S) / _nonstoichiometric_ (s) +# - *Timedness*: _timed_ (T) / _timeless_ (t) +# - *Having rate*: _having rate_ (R) / _not having rate_, _rateless_ (r) +# +# ==== Stoichiometricity +# +# I. For stoichiometric transitions: +# 1. Either *rate vector* is computed as *rate * stoichiometry vector*, +# 2. or *action vector* is computed a *action * stoichiometry vector*. +# II. For non-stoichiometric transitions: +# 1. Either *Rate vector* is obtained as the *rate closure result*, +# 2. or *action vector* is obtained as the *action closure result*. +# +# Summary: stoichiometricity distinguishes the *need to multiply the rate/action +# closure result by stoichiometry*. # -# ==== Having / not having rate -# * For transitions with rate, the closure result has to be -# multiplied by the time step duration (Δt) to get the action. -# * For rateless transitions, the closure result is used as is. +# ==== Having rate +# +# I. For transitions with rate, the closure *returns the rate*. The rate has to +# be multiplied by the time step (Δt) to get the action value. +# II. For transitions without rate (_rateless transitions_), the closure result +# directly specifies the action. # -# Conclusion: has_rate? distinguishes <b>the need to multiply the closure -# result by delta time</b> - differentiability of action by time. +# Summary: Having vs. not having rate distinguishes the *need to multiply the +# closure result by Δ time* -- differentiability of the action by time. # -# ==== Timed / Timeless -# * For timed transitions, action is time-dependent. Transitions with -# rate are thus always timed. In rateless transitions, timedness means -# that the action closure expects time step length (delta_t) as its first -# argument - its arity is thus codomain size + 1. -# * For timeless transitions, action is time-independent. Timeless -# transitions are necessarily also rateless. Arity of the action closure -# is expected to match the domain size. +# ==== Timedness # -# Conclusion: Transitions with rate are always timed. In rateless -# transitions, timedness distinguishes <b>the need to supply time step -# duration as the first argument to the action closure</b>. +# I. Timed transitions are defined as those, whose action has time as a +# parameter. Transitions with rate are thus always timed. For rateless +# transitions, being timed means that the action closure expects time step +# (Δt) as its first argument -- its arity is thus its codomain size + 1. +# II. Timeless transitions, in turn, are those, whose action is does not have +# time a parameter. Timeless transitions are necessarily also rateless. +# Arity of their action closure can be expected to match the domain size. +# +# Summary: In rateless transitions, timedness distinguishes the *need to supply +# time step duration as the first argument to the action closure*. Whereas the +# transitions with rate are always timed, and vice-versa, timeless transitions +# always rateless, there are only 6 instead of 2 ** 3 == 8 basic types. # # === Other transition types # -# ==== Assignment transitions -# Named argument :assignment_action set to true indicates that the -# transitions acts by replacing the object stored as place marking by -# the object supplied by the transition. (Same as in with spreadsheet -# functions.) For numeric types, same effect can be achieved by subtracting -# the old number from the place and subsequently adding the new value to it. +# ==== Assignment transitions (_A transitions_) +# If +:assignment_action+ is set to _true_, it indicates that the transition +# action entirely replaces the marking of its codomain with the result of its +# action closure -- like we are used to from spreadsheets. This behavior does +# not represent a truly novel type of a transition -- assignment transition is +# merely a *ts transition that cares to clear the codomain before adding the +# new value to it*. In other words, this behavior is (at least for numeric +# types) already achievable with ordinary ts transitions, and existence of +# specialized A transitions is just a convenience. # # ==== Functional / Functionless transitions -# Original Petri net definition does not speak about transition "functions", -# but it more or less assumes timeless action according to the stoichiometry. -# So in YPetri, stoichiometric transitions with no action / rate closure -# specified become functionless transitions as meant by Carl Adam Petri. +# YPetri is a domain model of _functional Petri nets_. Original Petri's +# definition does not speak about transition "functions". The transitions are +# defined as timeless and more or less assumed to be stoichiometric. Therefore, +# in +YPetri::Transition+ constructor, stoichiometric transitions with no +# function specified become functionless vanilla Petri net transitions. +# +# === "Discrete" vs. "continuous" in YPetri +# +# YPetri uses terminology of both "discrete" and "continuous" Petri nets. But +# in fact, in YPetri domain model, place marking is always considered discrete +# -- a discrete number of _tokens_, as defined by Carl Adam Petri. The meaning +# of _continuous_ in YPetri is different: A pragmatic measure of approximating +# this integer by a floating point number when the integer is so large, that the +# impact of this approximation is acceptable. The responsibility for the +# decision of how to represent the number of tokens is not a concern of the +# domain model, but only of the simulation method. Therefore, in YPetri, there +# are no _a priori_ "discrete" and "continuous" places or transitions. +# +# As for the transitions, terms _flux_ (flow), associated with continuous +# transitions, and _propensity_, associated with discrete stochastic +# transitions, are unified as _rate_. Again, the decision between "discrete" +# and "stochastic" is a concern of the simulation method, not the domain model. # class YPetri::Transition include NameMagic include YPetri::DependencyInjection @@ -195,18 +195,14 @@ # def rateless? not has_rate? end - # The term 'flux' (meaning flow) is associated with continuous transitions, - # while term 'propensity' is used with discrete stochastic transitions. - # By the design of YPetri, distinguishing between discrete and continuous - # computation is the responsibility of the simulation method, considering - # current marking of the transition's connectivity and quanta of its - # codomain. To emphasize unity of 'flux' and 'propensity', term 'rate' is - # used to represent both of them. Rate closure input arguments must - # correspond to the domain places. + # 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 @@ -283,129 +279,16 @@ # original specification anyway.) # def assignment_action?; @assignment_action end alias :assignment? :assignment_action? - # Result of the transition's "function", regardless of the #enabled? status. - # - def action Δt=nil - raise ArgumentError, "Δtime argument required for timed transitions!" if - timed? and Δt.nil? - # the code here looks awkward, because I was trying to speed it up - if has_rate? then - if stoichiometric? then - rate = rate_closure.( *domain_marking ) - stoichiometry.map { |coeff| rate * coeff * Δt } - else # assuming correct return value arity from the rate closure: - rate_closure.( *domain_marking ).map { |e| component * Δt } - end - else # rateless - if timed? then - if stoichiometric? then - rslt = action_closure.( Δt, *domain_marking ) - stoichiometry.map { |coeff| rslt * coeff } - else - action_closure.( Δt, *domain_marking ) # caveat result arity! - end - else # timeless - if stoichiometric? then - rslt = action_closure.( *domain_marking ) - stoichiometry.map { |coeff| rslt * coeff } - else - action_closure.( *domain_marking ) # caveat result arity! - end - end - end - end # action - # Zero action # def zero_action codomain.map { 0 } end - # Changes to the marking of codomain, as they would happen if #fire! was - # called right now (ie. honoring #enabled?, but not #cocked? status. - # - def action_after_feasibility_check( Δt=nil ) - raise AErr, "Δtime argument required for timed transitions!" if - timed? and Δt.nil? - act = Array( action Δt ) - # Assignment actions are always feasible - no need to check: - return act if assignment? - # check if the marking after the action would still be positive - enabled = codomain - .zip( act ) - .all? { |place, change| place.marking.to_f >= -change.to_f } - if enabled then act else - raise "firing of #{self}#{ Δt ? ' with Δtime %s' % Δt : '' } " + - "would result in negative marking" - zero_action - end - # LATER: This use of #zip here should be avoided for speed - end - - # Applies transition's action (adding/taking tokens) on its downstream - # places (aka. domain places). If the transition is timed, delta time has - # to be supplied as argument. In order for this method to work, the - # transition has to be cocked (#cock method), and firing uncocks the - # transition, so it has to be cocked again before it can be fired for - # the second time. If the transition is not cocked, this method has no - # effect. - # - def fire( Δt=nil ) - raise ArgumentError, "Δtime argument required for timed transitions!" if - timed? and Δt.nil? - return false unless cocked? - uncock - fire! Δt - return true - end - - # Fires the transition just like #fire method, but disregards the cocked / - # uncocked state of the transition. - # - def fire!( Δt=nil ) - raise ArgumentError, "Δt required for timed transitions!" if - Δt.nil? if timed? - try "to fire" do - if assignment_action? then - note has: "assignment action" - act = note "action", is: Array( action( Δt ) ) - codomain.each_with_index do |place, i| - "place #{place}".try "to assign marking #{i}" do - place.marking = act[i] - end - end - else - act = note "action", is: action_after_feasibility_check( Δt ) - codomain.each_with_index do |place, i| - "place #{place}".try "to assign marking #{i}" do - place.add act[i] - end - end - end - end - return nil - end - - # Sanity of execution is ensured by Petri's notion of transitions being - # "enabled" if and only if the intended action can immediately take - # place without getting places into forbidden state (negative marking). - # - def enabled?( Δt=nil ) - fail ArgumentError, "Δtime argument compulsory for timed transitions!" if - timed? && Δt.nil? - codomain.zip( action Δt ).all? do |place, change| - begin - place.guard.( place.marking + change ) - rescue YPetri::GuardError - false - end - end - end - # def lock # # LATER # end # alias :disable! :force_disabled @@ -443,10 +326,10 @@ end # Conversion to a string. # def to_s - "#<Transition: %s >" % + "#<Transition: %s>" % "#{name.nil? ? '' : '%s ' % name }(#{basic_type}%s)%s" % [ "#{assignment_action? ? ' Assign.' : ''}", "#{name.nil? ? ' id:%s' % object_id : ''}" ] end end # class YPetri::Transition