#encoding: utf-8
# Now is a good time to talk about transition classification:
#
# STOICHIOMETRIC / NON-STOICHIOMETRIC
# 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*.
#
# HAVING / NOT HAVING RATE
# 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.
#
# TIMED / TIMELESS
# 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.
#
# 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. For numeric types, same
# effect can be achieved by subtracting the old number from the place
# and subsequently adding the new value to it.
#
module YPetri
# Represents a Petri net transition. YPetri transitions come in 6
# basic types
#
# === Basic transition types
#
# * ts – timeless nonstoichiometric
# * tS – timeless stoichiometric
# * Tsr – timed rateless nonstoichiometric
# * TSr – timed rateless stoichiometric
# * sR – nonstoichiometric with rate
# * SR – stoichiometric with rate
#
# These 6 kinds of YPetri transitions correspond to the vertices
# of a cube with 3 dimensions:
#
# - stoichiometric (S) / nonstoichiometric (s)
# - timed (T) / timeless (t)
# - having rate (R) / not having rate (r)
#
# Since transitions with rate are always timed, and vice-versa, timeless
# transitions cannot have rate, there are only 6 permissible combinations,
# mentioned above.
#
# === Domain and codomin
#
# Each transition has a domain, or 'upstream places': A collection of places
# whose marking directly affects the transition's operation. Also, each
# transition has a codomain, or 'downstream places': A collection of places,
# whose marking is directly affected by the transition's operation.
#
# === Action and action vector
#
# Regardless of the type, every transition has action:
# 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 action vector. 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.
#
# === Meaning of the 3 basic transition properties
#
# ==== 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.
#
# Conclusion: stoichiometricity distinguishes 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.
#
# Conclusion: has_rate? distinguishes the need to multiply the closure
# result by delta time - differentiability of 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.
#
# 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.
#
# === 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.
#
# ==== 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.
#
class Transition
include NameMagic
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
# Names of upstream places.
#
def domain_pp; domain.map &:name end
alias :upstream_pp :domain_pp
# 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
# Names of downstream places.
#
def codomain_pp; codomain.map &:name end
alias :downstream_pp :codomain_pp
# Union of action arcs and test arcs.
#
def arcs; domain | codomain end
# Returns names of the (places connected to) the transition's arcs.
#
def aa; arcs.map &:name end
# 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.to_sym } end
# Does the transition have rate?
#
def has_rate?; @has_rate end
# Is the transition rateless?
#
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.
#
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 transition membership in one of 6 basic types of YPetri transitions:
# 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?
# Is the transition cocked?
#
# The transition has to be cocked before #fire method can be called
# successfully. (Can be overriden using #fire! method.)
#
def cocked?; @cocked end
# Opposite of #cocked?
#
def uncocked?; not cocked? end
# As you could have noted in the introduction, Transition class encompasses
# all different kinds of Petri net transitions. This is considered a good
# design pattern for cases like this, but it makes the transition class and
# its constructor look a bit complicated. Should you feel that way, please
# remember that you only learn one constructor, but can create many kinds
# of transition – the computer is doing a lot of work behind the scenes for
# you. The type of a transition created depends on the qualities of supplied
# arguments. However, you can also explicitly specify what kind of
# transition do you want, to exclude any ambiguity.
#
# Whatever arguments you supply, the constructor will always need a way to
# determine domain (upstream arcs) and codomain (downstream arcs) of your
# transitions, implicitly or explicitly. Secondly, the constructor must
# have a way to determine the transition's action, although there is more
# than one way of doing so. So enough talking and onto the examples. We
# will imagine having 3 places A, B, C, for which we will create various
# transitions:
#
# ==== Timeless nonstoichiometric (ts) transitions
# Action closure has to be supplied, whose return arity correspons 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 }
#
# (This represents a transition connected by arcs to places A, B, C, whose
# operation depends on C in such way, that if C.marking is positive,
# then half of the marking of A is shifted to B, while if C.marking is
# nonpositive, 1 is added to A.)
#
# ==== Timeless stoichiometric (tS) transitions
# Stochiometry has to be supplied, with optional action closure.
# Action closure return arity should be 1 (its result will be multiplied
# by the stoichiometry vector).
#
# If no action closure is given, a functionless transition will
# be created, whose action closure will be by default 1 * stoichiometry
# vector.
#
# ==== Timed rateless nonstoichiometric (Tsr) transitions
# Action closure has to be supplied, whose first argument is Δt, and the
# remaining arguments correspond to the domain size. Return arity of this
# closure should correspond to the codomain size.
#
# ==== Timed rateless stoichiometric (TSr) transitions
# Action closure has to be supplied, whose first argument is Δt, and the
# remaining arguments correspond to the domain size. Return arity of this
# closure should be 1 (to be multiplied by the stoichiometry vector).
#
# ==== Nonstoichiometric transitions with rate (sR)
# Rate closure has to be supplied, whose arity should correspond to the
# domain size (Δt argument is not needed). Return arity of this closure
# should correspond to the codomain size and represents rate of change
# contribution for marking of the codomain places.
#
# ==== Stoichiometric transitions with rate (SR)
#
# Rate closure and stoichiometry has to be supplied, whose arity should
# correspond to the domain size. Return arity of this closure should be 1
# (to be multiplied by the stoichiometry vector, as in all stoichiometric
# transitions).
#
# Transition( stoichiometry: { A: -1, B: 1 },
# rate: λ { |a| a * 0.5 } )
#
def initialize *args
# do the big work of checking in the arguments
check_in_arguments *args
# Inform upstream and downstream places they have been connected:
inform_upstream_places
inform_downstream_places
@cocked = false # transitions initialize uncocked
end
# Marking of the domain places.
#
def domain_marking
domain.map &:marking
end
# Marking of the codomain places.
#
def codomain_marking
codomain.map &:marking
end
# 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
# Allows #fire method to succeed. (#fire! disregards cocking.)
#
def cock; @cocked = true end
alias :cock! :cock
# Uncocks a cocked transition without firing it.
#
def uncock; @cocked = false end
alias :uncock! :uncock
# If #fire method of a transition applies its action (token adding/taking)
# on its domain, depending on codomain marking. Time step is expected as
# argument if the transition is timed. Only works if the transition has
# been cocked and causes the transition to uncock.
#
def fire( Δt=nil )
raise AErr, "Δtime argument required for timed transitions!" if
timed? and Δt.nil?
return false unless cocked?
uncock
fire! Δt
return true
end
# Fires the transition regardless of cocked/uncocked status.
#
def fire!( Δt=nil )
raise AErr, "Δtime required for timed transitions!" if timed? && Δt.nil?
if assignment_action? then
act = Array action( Δt )
codomain.each_with_index do |place, i|
place.marking = act[i]
end
else
act = action_after_feasibility_check( Δt )
codomain.each_with_index do |place, i|
place.add act[i]
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 )
raise AErr, "Δtime argument required for timed transitions!" if
timed? and Δt.nil?
codomain
.zip( action Δt )
.all? { |place, change| place.marking.to_f >= -change.to_f }
end
# Recursive firing of the upstream net portion (honors #cocked?).
#
def fire_upstream_recursively
return false unless cocked?
uncock
upstream_places.each &:fire_upstream_recursively
fire!
return true
end
# Recursive firing of the downstream net portion (honors #cocked?).
#
def fire_downstream_recursively
return false unless cocked?
uncock
fire!
downstream_places.each &:fire_downstream_recursively
return true
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
private
# **********************************************************************
# ARGUMENT CHECK-IN UPON INITIALIZATION
# **********************************************************************
# Checking in the arguments supplied to #initialize looks like a big job.
# I won't contest to that, but let us not, that it is basically nothing
# else then defining the duck type of the input argument collection.
# TypeError is therefore raised if invalid collection has been supplied.
#
def check_in_arguments *args
oo = args.extract_options!
oo.may_have :stoichiometry, syn!: [ :stoichio,
:s ]
oo.may_have :codomain, syn!: [ :codomain_arcs,
:codomain_places,
:downstream,
:downstream_arcs,
:downstream_places,
:action_arcs ]
oo.may_have :domain, syn!: [ :domain_arcs,
:domain_places,
:upstream,
:upstream_arcs,
:upstream_places ]
oo.may_have :rate, syn!: [ :flux,
:propensity,
:rate_closure,
:flux_closure,
:propensity_closure ]
oo.may_have :action, syn!: :action_closure
oo.may_have :timed
@has_rate = oo.has? :rate # was the rate was given?
# is the transition stoichiometric (S) or nonstoichiometric (s)?
@stoichiometric = oo.has? :stoichiometry
# downstream description arguments: codomain, stoichiometry (if S)
if stoichiometric? then
@codomain, @stoichiometry = check_in_downstream_description_for_S( oo )
else # s transitions have no stoichiometry
@codomain = check_in_downstream_description_for_s( oo )
end
# check in domain first, :missing symbol may appear
@domain = check_in_domain( oo )
# upstream description arguments; also takes care of :missing domain
if has_rate? then
@domain, @rate_closure, @timed, @functional =
check_in_upstream_description_for_R( oo )
else
@domain, @action_closure, @timed, @functional =
check_in_upstream_description_for_r( oo )
end
# optional assignment action:
@assignment_action = check_in_assignment_action( oo )
end # def check_in_arguments
# Makes sure that supplied collection consists only of appropriate places.
# Second optional argument customizes the error message.
#
def sanitize_place_collection place_collection, what_is_collection=nil
c = what_is_collection ? what_is_collection.capitalize : "Collection"
Array( place_collection ).map do |pl_id|
begin
place( pl_id )
rescue NameError
raise TypeError, "#{c} member #{pl_id} does not specify a valid place!"
end
end.aT what_is_collection, "not contain duplicate places" do |collection|
collection == collection.uniq
end
end
# Private method, part of #initialize argument checking-in.
#
def check_in_domain( oo )
if oo.has? :domain then
sanitize_place_collection( oo[:domain], "supplied domain" )
else
if stoichiometric? then
# take arcs with non-positive stoichiometry coefficients
Hash[ [ @codomain, @stoichiometry ].transpose ]
.delete_if{ |place, coeff| coeff > 0 }.keys
else
:missing
# Barring the caller's error, missing domain can mean:
# 1. empty domain
# 2. domain == codomain
# This will be figured later by rate/action closure arity
end
end
end
def check_in_upstream_description_for_R( oo )
_domain = domain # this method may modify domain
# check against colliding :action argument
raise TErr, "Rate & action are mutually exclusive!" if oo.has? :action
# lets figure the rate closure
rate_λ = case rate_arg = oo[:rate]
when Proc then # We received the closure directly,
# but we've to be concerned about missing domain.
if domain == :missing then # we've to figure user's intent
_domain = if rate_arg.arity == 0 then
[] # user meant empty domain
else
codomain # user meant domain same as codomain
end
else # domain not missing
raise TErr, "Rate closure arity (#{rate_arg.arity}) " +
"greater than domain size (#{domain.size})!" unless
rate_arg.arity.abs <= domain.size
end
rate_arg
else # We received something else,
# we must make assumption user's intent.
if stoichiometric? then # user's intent was mass action
raise TErr, "When a number is supplied as rate, domain " +
"must not be given!" if oo.has? :domain
construct_standard_mass_action( rate_arg )
else # user's intent was constant closure
raise TErr, "When rate is a number and no stoichiometry " +
"is supplied, codomain size must be 1!" unless
codomain.size == 1
# Missing domain is OK here,
_domain = [] if domain == :missing
# but if it was supplied explicitly, it must be empty.
raise TErr, "Rate is a number, but non-empty domain was " +
"supplied!" unless domain.empty? if oo.has?( :domain )
lambda { rate_arg }
end
end
# R transitions are implicitly timed
_timed = true
# check against colliding :timed argument
oo[:timed].tE :timed, "not be false if rate given" if oo.has? :timed
# R transitions are implicitly functional
_functional = true
return _domain, rate_λ, _timed, _functional
end
def check_in_upstream_description_for_r( oo )
_domain = domain # this method may modify domain
_functional = true
# was action closure was given explicitly?
if oo.has? :action then
action_λ = oo[:action].aT_is_a Proc, "supplied action named argument"
if oo.has? :timed then
_timed = oo[:timed]
# Time to worry about the domain_missing
if domain == :missing then # figure user's intent from closure arity
_domain = if action_λ.arity == ( _timed ? 1 : 0 ) then
[] # user meant empty domain
else
codomain # user meant domain same as codomain
end
else # domain not missing
raise TErr, "Rate closure arity (#{rate_arg.arity}) > domain " +
"size (#{domain.size})!" if action_λ.arity.abs > domain.size
end
else # :timed argument not supplied
if domain == :missing then
# If no domain was supplied, there is no way to reasonably figure
# out the user's intent, except when arity is 0:
_domain = case action_λ.arity
when 0 then
_timed = false
[] # empty domain is implied
else # no deduction of user intent possible
raise AErr, "Too much ambiguity: Neither domain nor " +
"timedness of the rateless transition was specified."
end
else # domain not missing
# Even if the user did not bother to inform us explicitly about
# timedness, we can use closure arity as a clue. If it equals the
# domain size, leaving no room for Δtime argument, the user intent
# was to create timeless transition. If it equals domain size + 1,
# theu user intended to create a timed transition.
_timed = case action_λ.arity
when domain.size then false
when domain.size + 1 then true
else # no deduction of user intent possible
raise AErr, "Timedness was not specified, and the " +
"arity of the action supplied action closure " +
"(#{action_λ.arity}) does not give clear hint on it."
end
end
end
else # rateless cases with no action closure specified
# Assumption must be made on transition's action. In particular,
# lambda { 1 } action closure will be assumed,
action_λ = lambda { 1 }
# and it will be required that the transition be stoichiometric and
# timeless. Domain will thus be required empty.
raise AErr, "Stoichiometry is compulsory, if rate/action was " +
"not supplied." unless stoichiometric?
# With this, we can drop worries about missing domain.
raise AErr, "When no rate/action is supplied, the transition can't " +
"be declared timed." if oo[:timed] if oo.has? :timed
_timed = false
_domain = []
_functional = false # the transition is considered functionless
end
return _domain, action_λ, _timed, _functional
end
def construct_standard_mass_action( num )
# assume standard mass-action law
nonpositive_coeffs = stoichiometry.select { |coeff| coeff <= 0 }
# the closure takes markings of the domain as its arguments
lambda { |*markings|
nonpositive_coeffs.size.times.reduce num do |acc, i|
marking, coeff = markings[ i ], nonpositive_coeffs[ i ]
# Stoichiometry coefficients equal to zero are taken to indicate
# plain factors, assuming that if these places were not involved
# in the transition at all, the user would not be mentioning them.
case coeff
when 0, -1 then marking * acc
else marking ** -coeff end
end
}
end
# Private method, checking in downstream specification from the argument
# field for stoichiometric transition.
#
def check_in_downstream_description_for_S( oo )
codomain, stoichio =
case oo[:stoichiometry]
when Hash then
# contains pairs { codomain place => stoichiometry coefficient }
raise AErr, "With hash-type stoichiometry, :codomain named " +
"argument must not be supplied." if oo.has? :codomain
oo[:stoichiometry].each_with_object [[], []] do |pair, memo|
codomain_place, stoichio_coeff = pair
memo[0] << codomain_place
memo[1] << stoichio_coeff
end
else
# array of stoichiometry coefficients
raise AErr, "With array-type stoichiometry, :codomain named " +
"argument must be supplied." unless oo.has? :codomain
[ oo[:codomain], Array( oo[:stoichiometry] ) ]
end
# enforce that stoichiometry is a collection of numbers
return sanitize_place_collection( codomain, "supplied codomain" ),
stoichio.aT_all_numeric( "supplied stoichiometry" )
end
# Private method, checking in downstream specification from the argument
# field for nonstoichiometric transition.
#
def check_in_downstream_description_for_s( oo )
# codomain must be explicitly given - no way around it:
raise AErr, "For non-stoichiometric transitions, :codomain named " +
"argument is compulsory." unless oo.has? :codomain
return sanitize_place_collection( oo[:codomain], "supplied codomain" )
end
# Private method, part of #initialize argument checking-in.
#
def check_in_assignment_action( oo )
if oo.has? :assignment_action, syn!: [ :assignment, :assign, :A ] then
if timed? then
msg = "Timed transitions may not have assignment action!"
raise TypeError, msg if oo[:assignment_action]
false
else # timeless transitions are eligible for assignment action
oo[:assignment_action]
end
else # if assignment action is not specified, false is
false
end
end
# Informs upstream places that they are connected to this transition.
#
def inform_upstream_places
upstream_places.each { |p| p.send :register_downstream_transition, self }
end
# Informs downstream places that they are connected to this transition.
#
def inform_downstream_places
downstream_places.each { |p| p.send :register_upstream_transition, self }
end
# Place class pertinent herein. Provided for the purpose of parametrized
# subclassing; expected to be overriden in the subclasses.
#
def Place
::YPetri::Place
end
# Transition class pertinent herein. Provided for the purpose of
# parametrized subclassing; expected to be overriden in the subclasses.
#
def Transition
::YPetri::Transition
end
# Net class pertinent herein. Provided for the purpose of parametrized
# subclassing; expected to be overriden in the subclasses.
#
def Net
::YPetri::Net
end
# Presents Place instance specified by the argument.
#
def place instance_identifier
Place().instance( instance_identifier )
end
# Presents Transition instance specified by the argument.
#
def transition instance_identifier
Transition().instance( instance_identifier )
end
# Presents Net instance specified by the argument.
#
def net instance_identifier
Net().instance( instance_identifier )
end
end # class Transition
end # module YPetri