# -*- coding: utf-8 -*-
require_relative 'dependency_injection'
require_relative 'transition/arcs'
require_relative 'transition/cocking'
require_relative 'transition/construction'
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 directly affects
# the transition. Downstream places are those, whose marking is directly affected
# by the transition.
#
# === Action and action vector
#
# Every transition has an _action_ -- the operation it represents. The action
# of _non-stoichiometric_ transitions is directly specified by the _action_
# _closure_ (whose output arity should match the codomain size.) For
# _stoichiometric_ transitions, the result of the action closure has to be
# multiplied by the transition's _stoichiometry_ _vector_ to obtain the action.
# Action of the _transitions_ _with_ _rate_ is specified indirectly by the
# _rate_ _closure_.
#
# === Rate
#
# In YPetri domain model, marking is always a discrete number of _tokens_ -- as
# Carl Adam Petri handed it down to us. YPetri recognizes the usefulness of
# representing a large number of tokens by a floating point number, but sees it
# as a pragmatic measure only. Other Petri net implementations often make class
# distincion between discrete and continuous places, and also distinguish between
# _flux_ ("flow" of the continous transitions) and _propensity_ (firing
# probability of discrete transitions). In YPetri, flux and propensity are
# unified under the term _rate_, and the choice between discrete and stochastic
# computation is seen as a concern of the simulation, not of the model.
#
# === Basic transition types
#
# There are 6 basic types of transitions in YPetri:
#
# * *ts* – timeless nonstoichiometric
# * *tS* – timeless stoichiometric
# * *Tsr* – timed rateless nonstoichiometric
# * *TSr* – timed rateless stoichiometric
# * *sR* – nonstoichiometric with rate
# * *SR* – stoichiometric with rate
#
# They arise by combining the 3 basic qualities:
#
# 1. *Stoichiometricity*: _stoichiometric_ (*S*) / _nonstoichiometric_ (*s*)
# 2. *Timedness*: _timed_ (*T*) / _timeless_ (*t*)
# 3. *Having* *rate*: having _rate_ (*R*) / not having rate (_rateless_) (*r*)
#
# ==== 1. Stoichiometricity
#
# * For *stoichiometric* transitions:
# - _either_ rate vector is obtained as
# rate * stoichiometry vector,
# - _or_ action vector is obtained as
# action * stoichiometry vector
# * For *non-stoichiometric* transitions:
# - _either_ rate vector is obtained as the rate closure result,
# - _or_ action vector is obtained as the action closure result.
#
# Summary: stoichiometricity distinguishes the need to multiply the
# rate/action closure result by stoichiometry.
#
# ==== 2. Having rate
#
# * Transitions *with* *rate* have a _rate_ _closure_, whose result is to be
# multiplied by +Δt+.
# * For transitions *without* *rate* (*rateless* transitions), the action
# closure specifies the action *directly*.
#
# Summary: Having vs. not having rate distinguishes the need to multiply the
# closure result by Δ time -- differentiability of the action by time.
#
# ==== 3. Timedness
#
# * Timed transitions are defined as those, whose action has time as a parameter.
# - Transitions with rate are therefore always timed.
# - For rateless transitions, being timed means, that their action closure
# expects Δt as its first argument -- arity thus equals codomain
# size + 1.
# * Timeless transitions are those, whose action does not have time as
# a parameter. Timeless transitions are necessarily also rateless.
#
# Summary: In rateless transitions, timedness distinguishes the need to
# supply time step duration as the first argument to the action closure.
# As the transitions with rate are necessarily timed, and timeless transitions
# necessarily rateless, there are only 6 instead of 2 ** 3 == 8 transition types.
#
# === Other transition attributes
#
# ==== Assignment transitions (_A_ _transitions_)
# If +:assignment_action+ option is set to _true_, it makes the transition
# entirely replace the codomain marking with its action closure result -- just
# like spreadsheet functions do. This, however, is just a convenience, and does
# not constitue a novel transition type, as it can be easily emulated by an
# ordinary ts transition caring to subtract the current domain marking before
# adding the desired values.
#
# ==== _Functional_ / _functionless_ transitions
# Other Petri net implementation often make a distinction between "ordinary"
# and "functional" transitions, where "ordinary" ("functionless") are the
# transitions as Carl Adam Petri handed them down to us. YPetri transtions
# are generally "functional", but the possibility of functionless transitions
# is also provided -- stoichiometric transitions with no action or rate
# specified become functionless transitions.
# 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.
#
class YPetri::Transition
include NameMagic
include YPetri::DependencyInjection
BASIC_TRANSITION_TYPES = {
ts: "timeless nonstoichiometric transition",
tS: "timeless stoichiometric transition",
Tsr: "timed rateless nonstoichiometric transition",
TSr: "timed rateless stoichiometric transition",
sR: "nonstoichiometric transition with rate",
SR: "stoichiometric transition with rate"
}
# 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
# 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
# Does the transition have rate?
#
def has_rate?
@has_rate
end
# Is the transition rateless?
#
def rateless?
not has_rate?
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
# Is the transition timeless? (Opposite of #timed?)
#
def timeless?
not timed?
end
# 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 6 basic types :
# 1. ts ..... timeless nonstoichiometric
# 2. tS ..... timeless stoichiometric
# 3. Tsr .... timed rateless nonstoichiometric
# 4. TSr .... timed rateless stoichiometric
# 5. sR ..... nonstoichiometric with rate
# 6. SR ..... stoichiometric with rate
#
def basic_type
if has_rate? then stoichiometric? ? :SR : :sR
elsif timed? then stoichiometric? ? :TSr : :Tsr
else stoichiometric? ? :tS : :ts end
end
# Reports transition's type (basic type + whether it's an assignment
# transition).
#
def type
assignment_action? ? "A(ts)" : basic_type
end
# Is it an assignment transition?
#
# A transition can be specified to have 'assignment action', in which case
# it completely replaces codomain marking with the objects resulting from
# the transition's action. Note that for numeric marking, specifying
# assignment action is a matter of convenience, not necessity, as it can
# be emulated by fully subtracting the present codomain values and adding
# the numbers computed by the transition to them. Assignment action flag
# is a matter of necessity only when codomain marking involves objects
# not supporting subtraction/addition (which is out of the scope of Petri's
# original specification anyway.)
#
def assignment_action?; @assignment_action end
alias :assignment? :assignment_action?
# 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 }(#{basic_type}%s)%s" %
[ "#{assignment_action? ? ' Assign.' : ''}",
"#{name.nil? ? ' id:%s' % object_id : ''}" ]
end
end # class YPetri::Transition