#encoding: utf-8
# Represents a Petri net: A collection of places and
# transitions. The connector arrows – called arcs in
# classical Petri net terminology – are considered a property
# of transitions. In YPetri, 'arcs' is a synonym for
# places / transitions connected to a given transition / place.
#
class YPetri::Net
include NameMagic
def initialize *args; oo = args.extract_options!
@places, @transitions = [], [] # empty arrays
# LATER: let the places/transitions be specified upon init
end
attr_reader :places, :transitions
# Names of places in the net.
#
def pp; places.map &:name end
# Names of transitions in the net.
#
def tt; transitions.map &:name end
# Includes a place in the net. Returns true if successful,
# false if the place is already included in the net.
#
def include_place! place
pl = place( place )
return false if @places.include? pl
@places << pl
return true
end
# Includes a transition in the net. Returns true if successful,
# false if the transition is already included in the net. The
# arcs of the transition being included may only connect to the places
# already in the net.
#
def include_transition! transition;
tr = transition( transition )
return false if @transitions.include? tr
raise TypeError, "Unable to include the transition #{tr} in #{self}: " +
"It connects to one or more places outside the net." unless
tr.arcs.all? { |pl| include? pl }
@transitions << tr
return true
end
# Excludes a place from the net. Returns true if successful,
# false if the place was not found in the net. A place may
# not be excluded from the net so long as any transitions in the
# net connect to it.
#
def exclude_place! place
pl = place( place )
raise "Unable to exclude #{pl} from #{self}: One or more transitions" +
"depend on it" if transitions.any? { |tr| tr.arcs.include? pl }
return true if @places.delete pl
return false
end
# Excludes a transition from the net. Returns true if successful,
# false if the transition was not found in the net.
#
def exclude_transition! transition
tr = transition( transition )
return true if @transitions.delete tr
return false
end
# Includes an object (either place or transition) in the net. Acts by
# calling #include_place! or #include_transition!, as needed, the
# difference being, that errors from bad arguments are swallowed.
#
def << place_or_transition
begin
include_place! place_or_transition
rescue NameError
begin
include_transition! place_or_transition
rescue NameError
raise NameError,
"Unrecognized place or transition: #{place_or_transition}"
end
end
return self
end
# Inquirer whether the net includes a place / transition.
#
def include? place_or_transition
pl = begin
place( place_or_transition )
rescue NameError
nil
end
return places.include? pl if pl
tr = begin
transition( place_or_transition )
rescue NameError
nil
end
return transitions.include? tr if tr
return false
end
# ----------------------------------------------------------------------
# Methods exposing transition collections acc. to their properties:
# Array of ts transitions in the net.
#
def timeless_nonstoichiometric_transitions
transitions.select { |t| t.timeless? && t.nonstoichiometric? }
end
alias ts_transitions timeless_nonstoichiometric_transitions
# Names of ts transitions in the net.
#
def timeless_nonstoichiometric_tt
timeless_nonstoichiometric_transitions.map &:name
end
alias ts_tt timeless_nonstoichiometric_tt
# Array of tsa transitions in the net.
#
def timeless_nonstoichiometric_nonassignment_transitions
transitions.select { |t|
t.timeless? && t.nonstoichiometric? && ! t.assignment_action?
}
end
alias tsa_transitions timeless_nonstoichiometric_nonassignment_transitions
# Names of tsa transitions in the net.
#
def timeless_nonstoichiometric_nonassignment_tt
timeless_nonstoichiometric_nonassignment_transitions.map &:name
end
alias tsa_tt timeless_nonstoichiometric_nonassignment_tt
# Array of tS transitions in the net.
#
def timeless_stoichiometric_transitions
transitions.select { |t| t.timeless? && t.stoichiometric? }
end
alias tS_transitions timeless_stoichiometric_transitions
# Names of tS transitions in the net.
#
def timeless_stoichiometric_tt
timeless_stoichiometric_transitions.map &:name
end
alias tS_tt timeless_stoichiometric_tt
# Array of Tsr transitions in the net.
#
def timed_nonstoichiometric_transitions_without_rate
transitions.select { |t| t.timed? && t.nonstoichiometric? && t.rateless? }
end
alias timed_rateless_nonstoichiometric_transitions \
timed_nonstoichiometric_transitions_without_rate
alias Tsr_transitions timed_nonstoichiometric_transitions_without_rate
# Names of Tsr transitions in the net.
#
def timed_nonstoichiometric_tt_without_rate
timed_nonstoichiometric_transitions_without_rate.map &:name
end
alias timed_rateless_nonstoichiometric_tt \
timed_nonstoichiometric_tt_without_rate
alias Tsr_tt timed_nonstoichiometric_tt_without_rate
# Array of TSr transitions in the net.
#
def timed_stoichiometric_transitions_without_rate
transitions.select { |t| t.timed? && t.stoichiometric? && t.rateless? }
end
alias timed_rateless_stoichiometric_transitions \
timed_stoichiometric_transitions_without_rate
alias TSr_transitions timed_stoichiometric_transitions_without_rate
# Names of TSr transitions in the net.
#
def timed_stoichiometric_tt_without_rate
timed_stoichiometric_transitions_without_rate.map &:name
end
alias timed_rateless_stoichiometric_tt timed_stoichiometric_tt_without_rate
alias Tsr_tt timed_stoichiometric_tt_without_rate
# Array of sR transitions in the net.
#
def nonstoichiometric_transitions_with_rate
transitions.select { |t| t.has_rate? && t.nonstoichiometric? }
end
alias sR_transitions nonstoichiometric_transitions_with_rate
# Names of sR transitions in the net.
#
def nonstoichiometric_tt_with_rate
nonstoichiometric_transitions_with_rate.map &:name
end
alias sR_tt nonstoichiometric_tt_with_rate
# Array of SR transitions in the net.
#
def stoichiometric_transitions_with_rate
transitions.select { |t| t.has_rate? and t.stoichiometric? }
end
alias SR_transitions stoichiometric_transitions_with_rate
# Names of SR transitions in the net.
#
def stoichiometric_tt_with_rate
stoichiometric_transitions_with_rate.map &:name
end
alias SR_tt stoichiometric_tt_with_rate
# Array of transitions with explicit assignment action
# (A transitions) in the net.
#
def assignment_transitions
transitions.select { |t| t.assignment_action? }
end
alias A_transitions assignment_transitions
# Names of transitions with explicit assignment action
# (A transitions) in the net.
#
def assignment_tt
assignment_transitions.map &:name
end
alias A_tt assignment_tt
# Array of stoichiometric transitions in the net.
#
def stoichiometric_transitions
transitions.select &:stoichiometric?
end
alias S_transitions stoichiometric_transitions
# Names of stoichiometric transitions in the net.
#
def stoichiometric_tt
stoichiometric_transitions.map &:name
end
alias S_tt stoichiometric_tt
# Array of nonstoichiometric transitions in the net.
#
def nonstoichiometric_transitions
transitions.select &:nonstoichiometric?
end
alias s_transitions nonstoichiometric_transitions
# Names of nonstoichimetric transitions in the net.
#
def nonstoichiometric_tt
nonstoichiometric_transitions.map &:name
end
alias s_tt nonstoichiometric_tt
# Array of timed transitions in the net.
#
def timed_transitions; transitions.select &:timed? end
alias T_transitions timed_transitions
# Names of timed transitions in the net.
#
def timed_tt; timed_transitions.map &:name end
alias T_tt timed_tt
# Array of timeless transitions in the net.
#
def timeless_transitions; transitions.select &:timeless? end
alias t_transitions timeless_transitions
# Names of timeless transitions in the net.
#
def timeless_tt; timeless_transitions.map &:name end
alias t_tt timeless_tt
# Array of transitions with rate in the net.
#
def transitions_with_rate; transitions.select &:has_rate? end
alias R_transitions transitions_with_rate
# Names of transitions with rate in the net.
#
def tt_with_rate; transitions_with_rate.map &:name end
alias R_tt tt_with_rate
# Array of rateless transitions in the net.
#
def rateless_transitions; transitions.select &:rateless? end
alias transitions_without_rate rateless_transitions
alias r_transitions rateless_transitions
# Names of rateless transitions in the net.
#
def rateless_tt; rateless_transitions.map &:name end
alias tt_without_rate rateless_tt
alias r_tt rateless_tt
# ==== Inquirer methods about net qualities
# Is the net functional?
#
def functional?; transitions.all? { |t| t.functional? } end
# Is the net timed?
#
def timed?; transitions.all? { |t| t.timed? } end
# ==== Simulation constructors
# Creates a new simulation from the net.
#
def new_simulation( **named_args )
YPetri::Simulation.new **named_args.merge( net: self )
end
# Creates a new timed simulation from the net.
#
def new_timed_simulation( **named_args )
YPetri::TimedSimulation.new **named_args.merge( net: self )
end
# ==== Sundry methods
# Networks are equal when their places and transitions are equal.
#
def == other
return false unless other.class_complies?( ç )
places == other.places && transitions == other.transitions
end
# Returns a string briefly describing the net.
#
def to_s
"#"
end
def visualize
require 'graphviz'
γ = GraphViz.new :G
# Add places and transitions.
place_nodes = places.map.with_object Hash.new do |pl, ꜧ|
ꜧ[pl] = γ.add_nodes pl.name.to_s,
fillcolor: 'lightgrey',
color: 'grey',
style: 'filled'
end
transition_nodes = transitions.map.with_object Hash.new do |tr, ꜧ|
ꜧ[tr] = γ.add_nodes tr.name.to_s,
shape: 'box',
fillcolor: if tr.assignment? then 'yellow'
elsif tr.basic_type == :SR then 'lightcyan'
else 'ghostwhite' end,
color: if tr.assignment? then 'goldenrod'
elsif tr.basic_type == :SR then 'cyan'
else 'grey' end,
style: 'filled'
end
# Add Petri net arcs.
transition_nodes.each { |tr, tr_node|
if tr.assignment? then
tr.codomain.each { |pl|
γ.add_edges tr_node, place_nodes[pl], color: 'goldenrod'
}
( tr.domain - tr.codomain ).each { |pl|
γ.add_edges tr_node, place_nodes[pl], color: 'grey', arrowhead: 'none'
}
elsif tr.basic_type == :SR then
tr.codomain.each { |pl|
if tr.stoichio[pl] > 0 then # producing arc
γ.add_edges tr_node, place_nodes[pl], color: 'cyan'
elsif tr.stoichio[pl] < 0 then # consuming arc
γ.add_edges place_nodes[pl], tr_node, color: 'cyan'
else
γ.add_edges place_nodes[pl], tr_node, color: 'grey', arrowhead: 'none'
end
}
( tr.domain - tr.codomain ).each { |pl|
γ.add_edges tr_node, place_nodes[pl], color: 'grey', arrowhead: 'none'
}
end
}
# Generate output image.
γ.output png: File.expand_path( "~/y_petri_graph.png" )
# require 'y_support/kde'
YSupport::KDE.show_file_with_kioclient File.expand_path( "~/y_petri_graph.png" )
end
# Inspect string of the instance.
#
def inspect; to_s end
private
# Display a file with kioclient (KDE).
#
def show_file_with_kioclient( file_name )
system "sleep 0.2; kioclient exec 'file:%s'" %
File.expand_path( '.', file_name )
end
# Place, Transition, Net classes.
#
def Place; ::YPetri::Place end
def Transition; ::YPetri::Transition end
def Net; ::YPetri::Net end
# Instance identification methods.
#
def place( which ); Place().instance( which ) end
def transition( which ); Transition().instance( which ) end
def net( which ); Net().instance( which ) end
end # class YPetri::Net