stamina-core 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/CHANGELOG.md +78 -0
  2. data/LICENCE.md +22 -0
  3. data/lib/stamina-core/stamina-core.rb +1 -0
  4. data/lib/stamina-core/stamina/adl.rb +298 -0
  5. data/lib/stamina-core/stamina/automaton.rb +1300 -0
  6. data/lib/stamina-core/stamina/automaton/complement.rb +26 -0
  7. data/lib/stamina-core/stamina/automaton/complete.rb +36 -0
  8. data/lib/stamina-core/stamina/automaton/compose.rb +111 -0
  9. data/lib/stamina-core/stamina/automaton/determinize.rb +104 -0
  10. data/lib/stamina-core/stamina/automaton/equivalence.rb +57 -0
  11. data/lib/stamina-core/stamina/automaton/hide.rb +41 -0
  12. data/lib/stamina-core/stamina/automaton/metrics.rb +77 -0
  13. data/lib/stamina-core/stamina/automaton/minimize.rb +23 -0
  14. data/lib/stamina-core/stamina/automaton/minimize/hopcroft.rb +118 -0
  15. data/lib/stamina-core/stamina/automaton/minimize/pitchies.rb +130 -0
  16. data/lib/stamina-core/stamina/automaton/strip.rb +16 -0
  17. data/lib/stamina-core/stamina/automaton/walking.rb +361 -0
  18. data/lib/stamina-core/stamina/command.rb +38 -0
  19. data/lib/stamina-core/stamina/command/adl2dot.rb +82 -0
  20. data/lib/stamina-core/stamina/command/help.rb +23 -0
  21. data/lib/stamina-core/stamina/command/robustness.rb +21 -0
  22. data/lib/stamina-core/stamina/command/run.rb +84 -0
  23. data/lib/stamina-core/stamina/core.rb +11 -0
  24. data/lib/stamina-core/stamina/dsl.rb +6 -0
  25. data/lib/stamina-core/stamina/dsl/automata.rb +23 -0
  26. data/lib/stamina-core/stamina/dsl/core.rb +14 -0
  27. data/lib/stamina-core/stamina/engine.rb +32 -0
  28. data/lib/stamina-core/stamina/engine/context.rb +35 -0
  29. data/lib/stamina-core/stamina/errors.rb +26 -0
  30. data/lib/stamina-core/stamina/ext/math.rb +19 -0
  31. data/lib/stamina-core/stamina/loader.rb +3 -0
  32. data/lib/stamina-core/stamina/markable.rb +42 -0
  33. data/lib/stamina-core/stamina/utils.rb +1 -0
  34. data/lib/stamina-core/stamina/utils/decorate.rb +81 -0
  35. data/lib/stamina-core/stamina/version.rb +14 -0
  36. metadata +93 -0
@@ -0,0 +1,26 @@
1
+ module Stamina
2
+ class Automaton
3
+
4
+ #
5
+ # Returns the complement automaton.
6
+ #
7
+ # A complement automaton is simply a complete automaton with all state
8
+ # labels flipped.
9
+ #
10
+ def complement
11
+ dup.complement!
12
+ end
13
+
14
+ #
15
+ # Complements this automaton
16
+ #
17
+ def complement!
18
+ complete!
19
+ each_state do |s|
20
+ s[:accepting] = !s.accepting?
21
+ end
22
+ self
23
+ end
24
+
25
+ end # class Automaton
26
+ end # module Stamina
@@ -0,0 +1,36 @@
1
+ module Stamina
2
+ class Automaton
3
+
4
+ #
5
+ # Checks if this automaton is complete
6
+ #
7
+ def complete?
8
+ alph = alphabet
9
+ states.find{|s| !(alphabet - s.out_symbols).empty?}.nil?
10
+ end
11
+
12
+ #
13
+ # Returns a completed copy of this automaton
14
+ #
15
+ def complete
16
+ self.dup.complete!
17
+ end
18
+
19
+ #
20
+ # Completes this automaton.
21
+ #
22
+ def complete!(sink_data = {:initial => false, :accepting => false, :error => false})
23
+ alph = alphabet
24
+ sink = add_state(sink_data)
25
+ each_state do |s|
26
+ out_symbols = s.out_symbols
27
+ (alph-out_symbols).each do |symbol|
28
+ connect(s, sink, symbol)
29
+ end
30
+ end
31
+ drop_state(sink) if sink.adjacent_states == [sink]
32
+ self
33
+ end
34
+
35
+ end # class Automaton
36
+ end # module Stamina
@@ -0,0 +1,111 @@
1
+ module Stamina
2
+ class Automaton
3
+ class Compose
4
+
5
+ # Automata under composition
6
+ attr_reader :automata
7
+
8
+ def initialize(automata)
9
+ @automata = automata.collect{|a|
10
+ a.deterministic? ? a : a.determinize
11
+ }
12
+ end
13
+
14
+ def marks(compound, initial = false)
15
+ {:initial => initial,
16
+ :accepting => compound.all?{|s| s.accepting?},
17
+ :error => compound.any?{|s| s.error?}
18
+ }
19
+ end
20
+
21
+ def main
22
+ # Map every symbol to concerned automata
23
+ symbols = Hash.new{|h,k| h[k] = []}
24
+
25
+ # The compound initial state
26
+ init = []
27
+
28
+ # Build the symbol table and prepare the initial state
29
+ automata.each_with_index do |fa,i|
30
+ fa.alphabet.each{|l| symbols[l][i] = fa}
31
+ init << fa.initial_state
32
+ end
33
+
34
+ # Compound automaton and states already seen
35
+ compound_fa = Automaton.new
36
+ map = {init => compound_fa.add_state(marks(init, true))}
37
+
38
+ # States to be visited
39
+ to_visit = [init]
40
+
41
+ until to_visit.empty?
42
+ source = to_visit.pop
43
+ symbols.each_pair do |symbol, automata|
44
+ catch(:avoid) do
45
+ # build the target state
46
+ target = source.zip(automata).collect{|ss,a|
47
+ if a.nil?
48
+ # this automaton does no synchronize on symbol
49
+ ss
50
+ elsif tt = ss.dfa_delta(symbol)
51
+ # it synchronizes and target has been found
52
+ tt
53
+ else
54
+ # it synchronizes but target has not been found
55
+ throw :avoid
56
+ end
57
+ }
58
+ unless map.has_key?(target)
59
+ map[target] = compound_fa.add_state(marks(target))
60
+ to_visit << target
61
+ end
62
+ compound_fa.connect(map[source],map[target],symbol)
63
+ end
64
+ end
65
+ end # to_visit.empty?
66
+
67
+ compound_fa
68
+ end
69
+
70
+ def self.execute(automata)
71
+ Compose.new(automata).main
72
+ end
73
+
74
+ end # class Compose
75
+
76
+ def compose(*automata)
77
+ Automaton::Compose.execute([self] + automata)
78
+ end
79
+
80
+ end # class Automaton
81
+ end # module Stamina
82
+
83
+ # class CompoundState
84
+ # include Enumerable
85
+ # attr_reader :states
86
+ #
87
+ # def initialize(states)
88
+ # @states = states
89
+ # end
90
+ #
91
+ # def hash
92
+ # states.hash
93
+ # end
94
+ #
95
+ # def ==(other)
96
+ # other.states == states
97
+ # end
98
+ # alias :eql? :other
99
+ #
100
+ # def marks(initial = false)
101
+ # {:initial => initial,
102
+ # :accepting => states.all?{|s| s.accepting?},
103
+ # :error => states.any?{|s| s.error?}
104
+ # }
105
+ # end
106
+ #
107
+ # def each
108
+ # states.each &Proc.new
109
+ # end
110
+ #
111
+ # end
@@ -0,0 +1,104 @@
1
+ module Stamina
2
+ class Automaton
3
+ class Determinize
4
+
5
+ class CompoundState
6
+
7
+ attr_reader :fa
8
+ attr_reader :states
9
+ attr_reader :initial
10
+
11
+ def initialize(fa, states, initial = false)
12
+ @fa = fa
13
+ @states = states.sort
14
+ @initial = initial
15
+ end
16
+
17
+ def empty?
18
+ states.empty?
19
+ end
20
+
21
+ def marks
22
+ @marks ||= begin
23
+ marks = {}
24
+ marks[:initial] = initial
25
+ marks[:accepting] = states.any?{|s| s.accepting?}
26
+ marks[:error] = states.any?{|s| s.error?}
27
+ marks
28
+ end
29
+ end
30
+
31
+ def delta(symbol)
32
+ CompoundState.new(fa, fa.delta(states, symbol))
33
+ end
34
+
35
+ def hash
36
+ @states.hash
37
+ end
38
+
39
+ def ==(other)
40
+ other.is_a?(CompoundState) &&
41
+ (other.fa == self.fa) &&
42
+ (other.states == self.states)
43
+ end
44
+ alias :eql? :==
45
+
46
+ end # class CompoundState
47
+
48
+ attr_reader :fa
49
+ attr_reader :options
50
+
51
+ def initialize(fa, options)
52
+ @fa = fa
53
+ @options = options
54
+ end
55
+
56
+ def main
57
+ # the alphabet
58
+ alph = fa.alphabet
59
+
60
+ # the minimized automaton
61
+ minimized = Automaton.new
62
+
63
+ # - map between compound states and minimized states
64
+ # - states to visit
65
+ map = {}
66
+ to_visit = []
67
+
68
+ # initial state and mark as to visit
69
+ init = CompoundState.new(fa, fa.initial_states, true)
70
+ map[init] = minimized.add_state(init.marks)
71
+ to_visit = [init]
72
+
73
+ until to_visit.empty?
74
+ current = to_visit.pop
75
+ alph.each do |symbol|
76
+ found = current.delta(symbol)
77
+ next if found.empty?
78
+ unless map.has_key?(found)
79
+ map[found] = minimized.add_state(found.marks)
80
+ to_visit << found
81
+ end
82
+ minimized.connect(map[current], map[found], symbol)
83
+ end
84
+ end
85
+
86
+ minimized
87
+ end
88
+
89
+ def self.execute(fa, options = {})
90
+ Determinize.new(fa, options).main
91
+ end
92
+
93
+ end # class Determinize
94
+
95
+ #
96
+ # Determinizes this automaton by removing explicit non-determinism as well
97
+ # as all espilon moves.
98
+ #
99
+ def determinize
100
+ Determinize.execute(self)
101
+ end
102
+
103
+ end # class Automaton
104
+ end # module Stamina
@@ -0,0 +1,57 @@
1
+ module Stamina
2
+ class Automaton
3
+
4
+ #
5
+ # Checks if this automaton is equivalent to another one.
6
+ #
7
+ # Automata must be both minimal and complete to guarantee that this method
8
+ # works.
9
+ #
10
+ def equivalent?(other, equiv = nil, key = :equiv_state)
11
+ equiv ||= Proc.new{|s1,s2| (s1 && s2) &&
12
+ (s1.accepting? == s2.accepting?) &&
13
+ (s1.error? == s2.error?) &&
14
+ (s1.initial? == s2.initial?) }
15
+
16
+ # Both must already have basic attributes in common
17
+ return false unless state_count==other.state_count
18
+ return false unless edge_count==other.edge_count
19
+ return false unless alphabet==other.alphabet
20
+ return false unless equiv[initial_state, other.initial_state]
21
+
22
+ # We instantiate the decoration algorithm for checking equivalence on this
23
+ # automaton:
24
+ # * decoration is the index of the equivalent state in other automaton
25
+ # * d0 is thus 'other.initial_state.index'
26
+ # * suppremum is identity and fails when the equivalent state is not unique
27
+ # * propagation checks transition function delta
28
+ #
29
+ algo = Stamina::Utils::Decorate.new(key)
30
+ algo.set_suppremum do |d0, d1|
31
+ if (d0.nil? or d1.nil?)
32
+ (d0 || d1)
33
+ elsif d0==d1
34
+ d0
35
+ else
36
+ raise Stamina::Abord
37
+ end
38
+ end
39
+ algo.set_propagate do |d,e|
40
+ reached = other.ith_state(d).dfa_step(e.symbol)
41
+ raise Stamina::Abord if reached.nil?
42
+ raise Stamina::Abord unless equiv[e.target, reached]
43
+ reached.index
44
+ end
45
+
46
+ # Run the algorithm now
47
+ begin
48
+ algo.execute(self, nil, other.initial_state.index)
49
+ return true
50
+ rescue Stamina::Abord
51
+ return false
52
+ end
53
+ end
54
+ alias :<=> :equivalent?
55
+
56
+ end # class Automaton
57
+ end # module Stamina
@@ -0,0 +1,41 @@
1
+ module Stamina
2
+ class Automaton
3
+
4
+ #
5
+ # Returns a copy of self where all symbols in `alph` have been
6
+ # replaced by nil.
7
+ #
8
+ def hide(alph)
9
+ dup.hide!(alph)
10
+ end
11
+
12
+ #
13
+ # Replaces all symbols in `alph` by nil in this automaton.
14
+ #
15
+ def hide!(alph)
16
+ new_alph = alphabet.to_a - alph.to_a
17
+ h = Set.new(new_alph.to_a)
18
+ each_edge do |edge|
19
+ edge.symbol = nil unless h.include?(edge.symbol)
20
+ end
21
+ self.alphabet = new_alph
22
+ self
23
+ end
24
+
25
+ #
26
+ # Returns a copy of self where all symbols not in `alph` have been
27
+ # replaced by nil.
28
+ #
29
+ def keep(alph)
30
+ dup.keep!(alph)
31
+ end
32
+
33
+ #
34
+ # Replaces all symbols not in `alph` by nil in this automaton.
35
+ #
36
+ def keep!(alph)
37
+ hide!(self.alphabet.to_a - alph.to_a)
38
+ end
39
+
40
+ end # class Automaton
41
+ end # module Stamina
@@ -0,0 +1,77 @@
1
+ module Stamina
2
+ class Automaton
3
+ #
4
+ # Provides useful metric methods on automata.
5
+ #
6
+ # This module is automatically included by Automaton and is not intended
7
+ # to be used directly.
8
+ #
9
+ module Metrics
10
+
11
+ #
12
+ # Returns the number of letters of the alphabet.
13
+ #
14
+ def alphabet_size
15
+ alphabet.size
16
+ end
17
+
18
+ #
19
+ # Returns the average degree of states, that is,
20
+ # <code>edge_count/state_count</code>
21
+ #
22
+ def avg_degree
23
+ edge_count.to_f/state_count.to_f
24
+ end
25
+ alias :avg_out_degree :avg_degree
26
+ alias :avg_in_degree :avg_degree
27
+
28
+ #
29
+ # Number of accepting states over all states
30
+ #
31
+ def accepting_ratio
32
+ states.select{|s|s.accepting?}.size.to_f/state_count.to_f
33
+ end
34
+
35
+ #
36
+ # Number of error states over all states
37
+ #
38
+ def error_ratio
39
+ states.select{|s|s.error?}.size.to_f/state_count.to_f
40
+ end
41
+
42
+ #
43
+ # Computes the depth of the automaton.
44
+ #
45
+ # The depth of an automaton is defined as the length of the longest shortest
46
+ # path from the initial state to a state.
47
+ #
48
+ # This method has a side effect on state marks, as it keeps the depth of
49
+ # each state as a mark under _key_, which defaults to :depth.
50
+ #
51
+ def depth(key = :depth)
52
+ algo = Stamina::Utils::Decorate.new(key)
53
+ algo.set_suppremum do |d0,d1|
54
+ # Here, unreached state has the max value (i.e. nil is +INF)
55
+ # and we look at the minimum depth for each state
56
+ if d0.nil?
57
+ d1
58
+ elsif d1.nil?
59
+ d0
60
+ else
61
+ (d0 <= d1 ? d0 : d1)
62
+ end
63
+ end
64
+ algo.set_propagate {|d,e| d+1 }
65
+ algo.execute(self, nil, 0)
66
+ deepest = states.max do |s0,s1|
67
+ # Here, we do not take unreachable states into account
68
+ # so that -1 is taken when nil is encountered
69
+ (s0[:depth] || -1) <=> (s1[:depth] || -1)
70
+ end
71
+ deepest[:depth]
72
+ end
73
+
74
+ end # module Metrics
75
+ include Metrics
76
+ end # class Automaton
77
+ end # module Stamina