Skip to content

Commit

Permalink
Refactor Utils::Decorate to accept an output object.
Browse files Browse the repository at this point in the history
  • Loading branch information
blambeau committed Jul 23, 2012
1 parent 7d93671 commit e5d7bc6
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 97 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
@@ -1,6 +1,7 @@
# 0.5.5 / FIX ME

* Generalized the decoration algorithm to work backwards as well as forwards.
* Generalized the decoration algorithm to work backwards as well as forwards as well as
accepting an output Hash-like object, defaulting to true state decorations.

# O.5.4 / 2012-03-06

Expand Down
19 changes: 9 additions & 10 deletions lib/stamina-core/stamina/automaton/equivalence.rb
Expand Up @@ -26,30 +26,29 @@ def equivalent?(other, equiv = nil, key = :equiv_state)
# * suppremum is identity and fails when the equivalent state is not unique
# * propagation checks transition function delta
#
algo = Stamina::Utils::Decorate.new(key)
algo = Stamina::Utils::Decorate.new
algo.set_suppremum do |d0, d1|
if (d0.nil? or d1.nil?)
(d0 || d1)
elsif d0==d1
d0
else
raise Stamina::Abord
throw :non_equivalent unless d0==d1
d0
end
end
algo.set_initiator{|s| s.initial? ? other.initial_state.index : nil}
algo.set_start_predicate{|s| s.initial? }
algo.set_propagate do |d,e|
reached = other.ith_state(d).dfa_step(e.symbol)
raise Stamina::Abord if reached.nil?
raise Stamina::Abord unless equiv[e.target, reached]
throw :non_equivalent unless reached && equiv[e.target, reached]
reached.index
end

# Run the algorithm now
begin
algo.execute(self, nil, other.initial_state.index)
catch(:non_equivalent) do
algo.call(self, key)
return true
rescue Stamina::Abord
return false
end
return false
end
alias :<=> :equivalent?

Expand Down
20 changes: 7 additions & 13 deletions lib/stamina-core/stamina/automaton/metrics.rb
Expand Up @@ -49,23 +49,17 @@ def error_ratio
# each state as a mark under _key_, which defaults to :depth.
#
def depth(key = :depth)
algo = Stamina::Utils::Decorate.new(key)
algo = Stamina::Utils::Decorate.new
algo.set_suppremum do |d0,d1|
# Here, unreached state has the max value (i.e. nil is +INF)
# and we look at the minimum depth for each state
if d0.nil?
d1
elsif d1.nil?
d0
else
(d0 <= d1 ? d0 : d1)
end
# Unreached state is MAX (i.e. nil is +INF); we look at the min depth for each state
(d0.nil? or d1.nil?) ? (d0 || d1) : (d0 <= d1 ? d0 : d1)
end
algo.set_propagate{|d,e| d+1 }
algo.execute(self, nil, 0)
algo.set_initiator{|s| s.initial? ? 0 : nil }
algo.set_start_predicate{|s| s.initial? }
algo.call(self, key)
deepest = states.max do |s0,s1|
# Here, we do not take unreachable states into account
# so that -1 is taken when nil is encountered
# do not take unreachable states into account: -1 is taken if nil is encountered
(s0[:depth] || -1) <=> (s1[:depth] || -1)
end
deepest[:depth]
Expand Down
145 changes: 90 additions & 55 deletions lib/stamina-core/stamina/utils/decorate.rb
@@ -1,25 +1,35 @@
module Stamina
module Utils
#
# Decorates states of an automaton by applying a propagation rule
# until a fix point is reached.
# Decorates states of an automaton by applying a propagation rule until a fix point is
# reached.
#
class Decorate

# The key to use to maintain the decoration on states (:invariant
# is used by default)
attr_writer :decoration_key

# Creates a decoration algorithm instance
def initialize(decoration_key = :invariant)
@decoration_key = decoration_key
def initialize(output = :invariant)
@output = Decorate.state_output(output) if output
@suppremum = nil
@propagate = nil
@initiator = nil
@backward = false
@start_predicate = nil
@backward = false
end

# Builds an output hash that keeps decoration in states
def self.state_output(decoration_key)
Object.new.extend Module.new{
define_method :[] do |state|
state[decoration_key]
end
define_method :[]= do |state,deco|
state[decoration_key] = deco
end
}
end

### CONFIGURATION ##################################################################

# Installs a suppremum function through a block.
def set_suppremum(&block)
raise ArgumentError, 'Suppremum expected through a block' if block.nil?
Expand All @@ -32,16 +42,6 @@ def suppremum=(proc)
set_suppremum(&proc)
end

# Computes the suppremum between two decorations. By default, this method
# looks for a suppremum function installed with set_suppremum. If not found,
# it tries calling a suppremum method on d0. If not found it raises an error.
# This method may be overriden.
def suppremum(d0, d1)
return @suppremum.call(d0, d1) if @suppremum
return d0.suppremum(d1) if d0.respond_to?(:suppremum)
raise "No suppremum function installed or implemented by decorations"
end

# Installs a propagate function through a block.
def set_propagate(&block)
raise ArgumentError, 'Propagate expected through a block' if block.nil?
Expand All @@ -54,16 +54,6 @@ def propagate=(proc)
set_propagate(&proc)
end

# Computes the propagation rule. By default, this method looks for a propagate
# function installed with set_propagate. If not found, it tries calling a +
# method on deco. If not found it raises an error.
# This method may be overriden.
def propagate(deco, edge)
return @propagate.call(deco, edge) if @propagate
return deco.+(edge) if deco.respond_to?(:+)
raise "No propagate function installed or implemented by decorations"
end

# Set an initiator methods, responsible of computing the initial decoration of
# each state
def set_initiator(&block)
Expand All @@ -77,12 +67,6 @@ def initiator=(proc)
set_initiator(&proc)
end

# Returns the initial decoration of state `s`
def init_deco(s)
return @initiator.call(s) if @initiator
raise "No initiator function installed"
end

# Sets the start predicate to use
def set_start_predicate(&block)
raise ArgumentError, 'Start predicate expected through a block' if block.nil?
Expand All @@ -95,50 +79,101 @@ def start_predicate=(proc)
set_start_predicate(&proc)
end

# Sets if the algorithms works backward
def backward=(val)
@backward = val
end

### SUBCLASS HOOKS #################################################################

# Computes the suppremum between two decorations. By default, this method
# looks for a suppremum function installed with set_suppremum. If not found,
# it tries calling a suppremum method on d0. If not found it raises an error.
# This method may be overriden.
def suppremum(d0, d1)
return @suppremum.call(d0, d1) if @suppremum
return d0.suppremum(d1) if d0.respond_to?(:suppremum)
raise "No suppremum function installed or implemented by decorations"
end

# Computes the propagation rule. By default, this method looks for a propagate
# function installed with set_propagate. If not found, it tries calling a +
# method on deco. If not found it raises an error.
# This method may be overriden.
def propagate(deco, edge)
return @propagate.call(deco, edge) if @propagate
return deco.+(edge) if deco.respond_to?(:+)
raise "No propagate function installed or implemented by decorations"
end

# Returns the initial decoration of state `s`
def init_deco(s)
return @initiator.call(s) if @initiator
raise "No initiator function installed"
end

# Returns the start predicate
def take_at_start?(s)
return @start_predicate.call(s) if @start_predicate
raise "No start predicate function installed"
end

# Sets if the algorithms works backward
def backward=(val)
@backward = val
end

# Work backward?
def backward?
@backward
end

### MAIN ###########################################################################

# Executes the propagation algorithm on a given automaton.
def call(fa)
fa.states.each do |s|
s[@decoration_key] = init_deco(s)
end
to_explore = fa.states.select{|s| take_at_start?(s)}
until to_explore.empty?
source = to_explore.pop
(backward? ? source.in_edges : source.out_edges).each do |edge|
target = backward? ? edge.source : edge.target
p_decor = propagate(source[@decoration_key], edge)
p_decor = suppremum(target[@decoration_key], p_decor)
unless p_decor == target[@decoration_key]
target[@decoration_key] = p_decor
to_explore << target unless to_explore.include?(target)
def call(fa, out = nil)
with_output(out) do |output|
fa.states.each{|s| output[s] = init_deco(s) } # Init decoration on each state
to_explore = fa.states.select{|s| take_at_start?(s)} # Init to_explore (start predicate)
until to_explore.empty? # empty to_explore now
source = to_explore.pop
each_edge_and_target(source) do |edge, target|
p_decor = propagate(output[source], edge)
p_decor = suppremum(output[target], p_decor)
unless p_decor == output[target]
output[target] = p_decor
to_explore << target unless to_explore.include?(target)
end
end
end
fa
end
fa
end

# Executes the propagation algorithm on a given automaton.
def execute(fa, bottom, d0)
warn "Decorate#execute is deprecated, use Decorate#call (#{caller[0]})"
self.initiator = lambda{|s| (s.initial? ? d0 : bottom)}
self.start_predicate = lambda{|s| s.initial? }
call(fa)
end

private

def with_output(out)
if out
yield out.is_a?(Symbol) ? Decorate.state_output(out) : out
elsif @output
warn "Decorate.new(:decokey) is deprecated, use Decorate#call(fa, :decokey) (#{caller[1]})"
yield @output
else
raise ArgumentError, "Output may not be nil", caller
end
end

def each_edge_and_target(source)
edges = backward? ? source.in_edges : source.out_edges
edges.each do |edge|
target = target = backward? ? edge.source : edge.target
yield edge, target
end
end

end # class Decorate
end # module Utils
end # module Stamina
18 changes: 11 additions & 7 deletions lib/stamina-induction/stamina/reg_lang/canonical_info.rb
Expand Up @@ -2,19 +2,23 @@ module Stamina
class RegLang
class CanonicalInfo

SHORT_PREFIXES = begin
algo = Stamina::Utils::Decorate.new(:short_prefix)
algo.set_suppremum do |d0,d1|
class ShortPrefixes < Utils::Decorate
def suppremum(d0, d1)
if (d0.nil? || d1.nil?)
(d0 || d1)
else
d0.size <= d1.size ? d0 : d1
(d0.size <= d1.size) ? d0 : d1
end
end
algo.set_propagate do |deco, edge|
def propagate(deco, edge)
deco.dup << edge.symbol
end
algo
def init_deco(s)
s.initial? ? [] : nil
end
def take_at_start?(s)
s.initial?
end
end

attr_reader :cdfa
Expand Down Expand Up @@ -105,7 +109,7 @@ def characteristic_sample
# Ensures that short prefixes of states are recognized
def prefixes!
unless defined?(@prefixes)
SHORT_PREFIXES.execute(cdfa, nil, [])
ShortPrefixes.new.call(cdfa, :short_prefix)
@prefixes = true
end
end
Expand Down

0 comments on commit e5d7bc6

Please sign in to comment.