stamina 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. data/CHANGELOG.md +24 -0
  2. data/Gemfile.lock +5 -1
  3. data/bin/stamina +10 -0
  4. data/lib/stamina.rb +2 -1
  5. data/lib/stamina/abbadingo.rb +2 -0
  6. data/lib/stamina/abbadingo/random_dfa.rb +48 -0
  7. data/lib/stamina/abbadingo/random_sample.rb +146 -0
  8. data/lib/stamina/adl.rb +6 -6
  9. data/lib/stamina/automaton.rb +29 -4
  10. data/lib/stamina/automaton/complete.rb +36 -0
  11. data/lib/stamina/automaton/equivalence.rb +55 -0
  12. data/lib/stamina/automaton/metrics.rb +8 -1
  13. data/lib/stamina/automaton/minimize.rb +25 -0
  14. data/lib/stamina/automaton/minimize/hopcroft.rb +116 -0
  15. data/lib/stamina/automaton/minimize/pitchies.rb +64 -0
  16. data/lib/stamina/automaton/strip.rb +16 -0
  17. data/lib/stamina/automaton/walking.rb +46 -19
  18. data/lib/stamina/command.rb +45 -0
  19. data/lib/stamina/command/abbadingo_dfa.rb +81 -0
  20. data/lib/stamina/command/abbadingo_samples.rb +40 -0
  21. data/lib/stamina/command/adl2dot.rb +71 -0
  22. data/lib/stamina/command/classify.rb +48 -0
  23. data/lib/stamina/command/help.rb +27 -0
  24. data/lib/stamina/command/infer.rb +141 -0
  25. data/lib/stamina/command/metrics.rb +51 -0
  26. data/lib/stamina/command/robustness.rb +22 -0
  27. data/lib/stamina/command/score.rb +35 -0
  28. data/lib/stamina/errors.rb +4 -1
  29. data/lib/stamina/ext/math.rb +20 -0
  30. data/lib/stamina/induction/{redblue.rb → blue_fringe.rb} +29 -28
  31. data/lib/stamina/induction/commons.rb +32 -46
  32. data/lib/stamina/induction/rpni.rb +7 -9
  33. data/lib/stamina/induction/union_find.rb +3 -3
  34. data/lib/stamina/loader.rb +1 -0
  35. data/lib/stamina/sample.rb +79 -2
  36. data/lib/stamina/scoring.rb +37 -0
  37. data/lib/stamina/version.rb +2 -2
  38. data/stamina.gemspec +2 -1
  39. data/stamina.noespec +9 -12
  40. data/test/stamina/abbadingo/random_dfa_test.rb +16 -0
  41. data/test/stamina/abbadingo/random_sample_test.rb +78 -0
  42. data/test/stamina/adl_test.rb +27 -2
  43. data/test/stamina/automaton/complete_test.rb +58 -0
  44. data/test/stamina/automaton/equivalence_test.rb +120 -0
  45. data/test/stamina/automaton/minimize/hopcroft_test.rb +15 -0
  46. data/test/stamina/automaton/minimize/minimize_test.rb +55 -0
  47. data/test/stamina/automaton/minimize/pitchies_test.rb +15 -0
  48. data/test/stamina/automaton/minimize/rice_edu_10.adl +16 -0
  49. data/test/stamina/automaton/minimize/rice_edu_10.min.adl +13 -0
  50. data/test/stamina/automaton/minimize/rice_edu_13.adl +13 -0
  51. data/test/stamina/automaton/minimize/rice_edu_13.min.adl +7 -0
  52. data/test/stamina/automaton/minimize/should_strip_1.adl +8 -0
  53. data/test/stamina/automaton/minimize/should_strip_1.min.adl +6 -0
  54. data/test/stamina/automaton/minimize/unknown_1.adl +16 -0
  55. data/test/stamina/automaton/minimize/unknown_1.min.adl +12 -0
  56. data/test/stamina/automaton/strip_test.rb +36 -0
  57. data/test/stamina/automaton/walking/dfa_delta_test.rb +39 -0
  58. data/test/stamina/automaton_test.rb +13 -1
  59. data/test/stamina/induction/{redblue_test.rb → blue_fringe_test.rb} +22 -22
  60. data/test/stamina/sample_test.rb +75 -0
  61. data/test/stamina/stamina_test.rb +13 -2
  62. metadata +98 -23
  63. data/bin/adl2dot +0 -12
  64. data/bin/classify +0 -12
  65. data/bin/redblue +0 -12
  66. data/bin/rpni +0 -12
  67. data/lib/stamina/command/adl2dot_command.rb +0 -73
  68. data/lib/stamina/command/classify_command.rb +0 -57
  69. data/lib/stamina/command/redblue_command.rb +0 -58
  70. data/lib/stamina/command/rpni_command.rb +0 -58
  71. data/lib/stamina/command/stamina_command.rb +0 -79
@@ -171,6 +171,43 @@ module Stamina
171
171
  end
172
172
  alias :hbcr :harmonic_balanced_classification_rate
173
173
  alias :harmonic_bcr :harmonic_balanced_classification_rate
174
+
175
+ MEASURES = [
176
+ :false_positive, :false_negative,
177
+ :true_positive, :true_negative,
178
+ :accuracy, :error_rate,
179
+ :precision, :recall, :f_measure,
180
+ :false_positive_rate, :false_negative_rate,
181
+ :true_positive_rate, :true_negative_rate,
182
+ :positive_predictive_value, :negative_predictive_value,
183
+ :sensitivity, :specificity,
184
+ :positive_likelihood, :negative_likelihood,
185
+ :balanced_classification_rate, :balanced_error_rate, :harmonic_bcr
186
+ ]
187
+
188
+ def to_h
189
+ h = {}
190
+ MEASURES.each do |m|
191
+ h[m] = self.send(m.to_sym)
192
+ end
193
+ h
194
+ end
195
+
196
+ def to_s
197
+ s = ""
198
+ MEASURES.each do |m|
199
+ vals = case val = self.send(m.to_sym)
200
+ when Integer
201
+ "%s" % val
202
+ when Float
203
+ "%.5f" % val
204
+ else
205
+ "%s" % val
206
+ end
207
+ s += "%30s: %10s\n" % [m.to_s, vals]
208
+ end
209
+ s
210
+ end
174
211
 
175
212
  end # module Scoring
176
213
  end # module Stamina
@@ -2,8 +2,8 @@ module Stamina
2
2
  module Version
3
3
 
4
4
  MAJOR = 0
5
- MINOR = 3
6
- TINY = 1
5
+ MINOR = 4
6
+ TINY = 0
7
7
 
8
8
  def self.to_s
9
9
  [ MAJOR, MINOR, TINY ].join('.')
data/stamina.gemspec CHANGED
@@ -129,7 +129,8 @@ Gem::Specification.new do |s|
129
129
  s.add_development_dependency("yard", "~> 0.6.4")
130
130
  s.add_development_dependency("bluecloth", "~> 2.0.9")
131
131
  s.add_development_dependency("wlang", "~> 0.10.1")
132
-
132
+ s.add_development_dependency("gnuplot", "~> 2.3.6")
133
+ s.add_dependency("quickl", "~> 0.2.0")
133
134
 
134
135
  # The version of ruby required by this gem
135
136
  #
data/stamina.noespec CHANGED
@@ -9,7 +9,7 @@ variables:
9
9
  upper:
10
10
  Stamina
11
11
  version:
12
- 0.3.1
12
+ 0.4.0
13
13
  summary: |-
14
14
  Automaton and Regular Inference Toolkit
15
15
  description: |-
@@ -22,14 +22,11 @@ variables:
22
22
  - http://stamina.chefbe.net/
23
23
  - http://github.com/blambeau/stamina
24
24
  dependencies:
25
- # Rake is required for developers, as usual
26
- - {name: rake, version: "~> 0.8.7", groups: [development]}
27
- # Bundler is required for developers and is used by the Rakefile
28
- - {name: bundler, version: "~> 1.0", groups: [development]}
29
- # RSpec is required to run 'rake spec'. See tasks/spec.rake
30
- - {name: rspec, version: "~> 2.4.0", groups: [development]}
31
- # YARD and BlueCloth are required to run 'rake yard'. See tasks/yard.rake
32
- - {name: yard, version: "~> 0.6.4", groups: [development]}
33
- - {name: bluecloth, version: "~> 2.0.9", groups: [development]}
34
- # wlang is required to run 'rake debug_mail'. See tasks/debug_mail.rake
35
- - {name: wlang, version: "~> 0.10.1", groups: [development]}
25
+ - {name: quickl, version: "~> 0.2.0", groups: [runtime]}
26
+ - {name: rake, version: "~> 0.8.7", groups: [development]}
27
+ - {name: bundler, version: "~> 1.0", groups: [development]}
28
+ - {name: rspec, version: "~> 2.4.0", groups: [development]}
29
+ - {name: yard, version: "~> 0.6.4", groups: [development]}
30
+ - {name: bluecloth, version: "~> 2.0.9", groups: [development]}
31
+ - {name: wlang, version: "~> 0.10.1", groups: [development]}
32
+ - {name: gnuplot, version: "~> 2.3.6", groups: [development]}
@@ -0,0 +1,16 @@
1
+ require 'stamina/stamina_test'
2
+ require 'stamina/abbadingo'
3
+ module Stamina
4
+ module Abbadingo
5
+ class RandomDFATest < StaminaTest
6
+
7
+ def test_it_looks_ok_with_default_options
8
+ dfa = RandomDFA.new(32).execute
9
+ assert dfa.deterministic?
10
+ assert dfa.minimal?
11
+ assert dfa.complete?
12
+ end
13
+
14
+ end # class RandomDFATest
15
+ end # module Abbadingo
16
+ end # module Stamina
@@ -0,0 +1,78 @@
1
+ require 'stamina/stamina_test'
2
+ require 'stamina/abbadingo'
3
+ module Stamina
4
+ module Abbadingo
5
+ class RandomSampleTest < StaminaTest
6
+
7
+ def test_length_for
8
+ rs = RandomSample::StringEnumerator.new
9
+ assert_equal 0, rs.length_for(1)
10
+ assert_equal 1, rs.length_for(2)
11
+ assert_equal 1, rs.length_for(3)
12
+ assert_equal 2, rs.length_for(4)
13
+ assert_equal 2, rs.length_for(5)
14
+ assert_equal 2, rs.length_for(6)
15
+ assert_equal 2, rs.length_for(7)
16
+ assert_equal 3, rs.length_for(8)
17
+ end
18
+
19
+ def test_string_for
20
+ rs = RandomSample::StringEnumerator.new
21
+ assert_equal [], rs.string_for(1)
22
+ assert_equal ["0"], rs.string_for(2)
23
+ assert_equal ["1"], rs.string_for(3)
24
+ assert_equal ["0", "0"], rs.string_for(4)
25
+ assert_equal ["1", "0"], rs.string_for(5)
26
+ assert_equal ["0", "1"], rs.string_for(6)
27
+ assert_equal ["1", "1"], rs.string_for(7)
28
+ end
29
+
30
+ def test_string_for_generates_all_diff
31
+ rs = RandomSample::StringEnumerator.new
32
+ h = {}
33
+ (1..100).each{|i| h[rs.string_for(i)] = true}
34
+ assert_equal 100, h.size
35
+ end
36
+
37
+ def test_string_for_respects_distribution
38
+ rs = RandomSample::StringEnumerator.new
39
+ lengths = Hash.new{|h,k| h[k] = 0}
40
+ (1..127).each{|i| lengths[rs.string_for(i).size] += 1}
41
+ assert_equal [0, 1, 2, 3, 4, 5, 6], lengths.keys.sort
42
+ prop = (0..6).collect{|i| lengths[i].to_f/128}
43
+ assert_equal [0.0078125, 0.015625, 0.03125, 0.0625, 0.125, 0.25, 0.5], prop
44
+ end
45
+
46
+ def test_enumerator
47
+ enum = RandomSample::StringEnumerator.new(10)
48
+ lengths = Hash.new{|h,k| h[k] = 0}
49
+ 20000.times{lengths[enum.one.size] += 1}
50
+ assert (lengths.keys.sort - (0..10).to_a).empty?
51
+ prop = (0..10).collect{|i| lengths[i].to_f/20000}
52
+ assert((prop[-1] >= 0.45) && (prop[-1] <= 0.55))
53
+ assert((prop[-2] >= 0.2) && (prop[-2] <= 0.3))
54
+ assert((prop[-3] >= 0.1) && (prop[-3] <= 0.15))
55
+ end
56
+
57
+ def test_it_can_be_used_on_small_dfas
58
+ dfa = RandomDFA.new(16).execute
59
+ training, test = RandomSample.execute(dfa)
60
+
61
+ assert test.size > 0
62
+ assert training.size > 0
63
+
64
+ # check training sample
65
+ assert training.positive_count > 0
66
+ assert training.negative_count > 0
67
+ assert dfa.correctly_classify?(training)
68
+
69
+ # check test sample
70
+ assert test.positive_count > 0
71
+ assert test.negative_count > 0
72
+ assert dfa.correctly_classify?(test)
73
+ end
74
+
75
+
76
+ end # class RandomDFATest
77
+ end # module Abbadingo
78
+ end # module Stamina
@@ -486,6 +486,31 @@ module Stamina
486
486
  EOF
487
487
  end
488
488
  end
489
+
490
+ def test_allows_error_states
491
+ dfa = ADL::parse_automaton <<-EOF
492
+ 5 0
493
+ 0 true true true
494
+ 1 false false true
495
+ 2 false false false
496
+ 3 false true false
497
+ 4 false true
498
+ EOF
499
+ assert dfa.ith_state(0).accepting? && dfa.ith_state(0).error?
500
+ assert !dfa.ith_state(1).accepting? && dfa.ith_state(1).error?
501
+ assert !dfa.ith_state(2).accepting? && !dfa.ith_state(2).error?
502
+ assert dfa.ith_state(3).accepting? && !dfa.ith_state(3).error?
503
+ assert !dfa.ith_state(4).error?
504
+ end
505
+
506
+ def test_flushes_error_states
507
+ dfa = ADL::parse_automaton <<-EOF
508
+ 2 0
509
+ 0 true false
510
+ 1 false false true
511
+ EOF
512
+ assert_equal "1 false false true", dfa.to_adl.split("\n")[2].strip
513
+ end
514
+
489
515
  end # class ADLTest
490
-
491
- end # module Stamina
516
+ end # module Stamina
@@ -0,0 +1,58 @@
1
+ require 'test/unit'
2
+ require 'stamina/stamina_test'
3
+ module Stamina
4
+ class Automaton
5
+ class CompleteTest < StaminaTest
6
+
7
+ def test_on_not_complete
8
+ x, y, z = nil, nil, nil
9
+ dfa = Automaton.new(true) do |fa|
10
+ fa.alphabet = ["a", "b"]
11
+ x = fa.add_state(:initial => true, :accepting => true)
12
+ y = fa.add_state(:initial => false, :accepting => false)
13
+ fa.connect(0,1,'a')
14
+ fa.connect(1,0,'b')
15
+ end
16
+
17
+ assert_equal false, dfa.complete?
18
+ dfa.complete!
19
+ assert_equal true, dfa.complete?
20
+
21
+ assert_equal 3, dfa.state_count
22
+ z = dfa.ith_state(2)
23
+ assert_equal z, dfa.dfa_delta(x, "b")
24
+ assert_equal y, dfa.dfa_delta(x, "a")
25
+ assert_equal z, dfa.dfa_delta(y, "a")
26
+ assert_equal x, dfa.dfa_delta(y, "b")
27
+ end
28
+
29
+ def test_on_complete
30
+ dfa = Automaton.new(true) do |fa|
31
+ fa.alphabet = ["a"]
32
+ fa.add_state(:initial => true, :accepting => true)
33
+ fa.add_state(:initial => false, :accepting => false)
34
+ fa.connect(0,1,'a')
35
+ fa.connect(1,0,'a')
36
+ end
37
+ assert_equal true, dfa.complete?
38
+ dfa.complete!
39
+ assert_equal 2, dfa.state_count
40
+ end
41
+
42
+ def test_it_has_a_non_touching_impl
43
+ dfa = Automaton.new(true) do |fa|
44
+ fa.alphabet = ["a", "b"]
45
+ fa.add_state(:initial => true, :accepting => true)
46
+ fa.add_state(:initial => false, :accepting => false)
47
+ fa.connect(0,1,'a')
48
+ fa.connect(1,0,'b')
49
+ end
50
+ c = dfa.complete
51
+ assert_equal 2, dfa.state_count
52
+ assert_equal 3, c.state_count
53
+ end
54
+
55
+ end # class CompleteTest
56
+ end # class Automaton
57
+ end # module Stamina
58
+
@@ -0,0 +1,120 @@
1
+ require 'test/unit'
2
+ require 'stamina/stamina_test'
3
+ module Stamina
4
+ class Automaton
5
+ class EquivalenceTest < StaminaTest
6
+
7
+ def test_equivalence_on_small_dfa
8
+ assert_equal true, @small_dfa <=> @small_dfa
9
+ end
10
+
11
+ def test_equivalence_on_real_case
12
+ dfa1 = Stamina::ADL.parse_automaton <<-EOF
13
+ 3 5
14
+ 0 true false
15
+ 1 false false
16
+ 2 false true
17
+ 0 1 a
18
+ 1 1 a
19
+ 1 2 b
20
+ 2 2 b
21
+ 0 2 b
22
+ EOF
23
+ dfa2 = Stamina::ADL.parse_automaton <<-EOF
24
+ 3 5
25
+ 0 false true
26
+ 1 true false
27
+ 2 false false
28
+ 0 0 b
29
+ 1 2 a
30
+ 1 0 b
31
+ 2 2 a
32
+ 2 0 b
33
+ EOF
34
+ dfa3 = Stamina::ADL.parse_automaton <<-EOF
35
+ 3 5
36
+ 0 false false
37
+ 1 false true
38
+ 2 true false
39
+ 0 0 a
40
+ 0 1 b
41
+ 1 1 b
42
+ 2 0 a
43
+ 2 1 b
44
+ EOF
45
+ assert_equal true, dfa1 <=> dfa2
46
+ assert_equal true, dfa2 <=> dfa1
47
+ assert_equal true, dfa1 <=> dfa3
48
+ assert_equal true, dfa3 <=> dfa1
49
+ assert_equal true, dfa2 <=> dfa3
50
+ assert_equal true, dfa3 <=> dfa2
51
+ end
52
+
53
+ def test_equivalence_does_not_change_the_automata
54
+ dfa1 = Stamina::ADL.parse_automaton <<-EOF
55
+ 1 1
56
+ 0 true true
57
+ 0 0 a
58
+ EOF
59
+ assert_not_nil dfa1.initial_state
60
+ assert_equal true, dfa1 <=> dfa1
61
+ assert_not_nil dfa1.initial_state
62
+ end
63
+
64
+ def test_non_equivalent_dfa_are_recognized_1
65
+ dfa1 = Stamina::ADL.parse_automaton <<-EOF
66
+ 3 5
67
+ 0 true false
68
+ 1 false false
69
+ 2 false true
70
+ 0 1 a
71
+ 1 1 a
72
+ 1 2 b
73
+ 2 2 b
74
+ 0 2 b
75
+ EOF
76
+ assert_equal false, @small_dfa <=> dfa1
77
+ assert_equal false, dfa1 <=> @small_dfa
78
+ end
79
+
80
+ def test_non_equivalent_dfa_are_recognized_2
81
+ dfa1 = Stamina::ADL.parse_automaton <<-EOF
82
+ 5 4
83
+ 0 true false
84
+ 1 false false
85
+ 2 false false
86
+ 3 false false
87
+ 4 false false
88
+ 0 1 a
89
+ 1 2 a
90
+ 2 3 a
91
+ 3 4 a
92
+ EOF
93
+ dfa2 = Stamina::ADL.parse_automaton <<-EOF
94
+ 1 1
95
+ 0 true true
96
+ 0 0 a
97
+ EOF
98
+ assert_equal false, dfa2 <=> dfa1
99
+ assert_not_nil dfa1.initial_state
100
+ assert_not_nil dfa2.initial_state
101
+ assert_equal false, dfa1 <=> dfa2
102
+ end
103
+
104
+ def test_equivalence_takes_care_of_state_flags
105
+ dfa1 = Stamina::ADL.parse_automaton <<-EOF
106
+ 1 0
107
+ 0 true false
108
+ EOF
109
+ dfa2 = Stamina::ADL.parse_automaton <<-EOF
110
+ 1 0
111
+ 0 true true
112
+ EOF
113
+ assert_equal false, dfa1 <=> dfa2
114
+ assert_equal false, dfa2 <=> dfa1
115
+ end
116
+
117
+ end # class EquivalenceTest
118
+ end # class Automaton
119
+ end # module Stamina
120
+
@@ -0,0 +1,15 @@
1
+ require File.expand_path("../minimize_test", __FILE__)
2
+ module Stamina
3
+ class Automaton
4
+ module Minimize
5
+ class HopcroftTest < MinimizeTest
6
+
7
+ def algo
8
+ Hopcroft
9
+ end
10
+
11
+ end # class HopcroftTest
12
+ end # module Minimize
13
+ end # class Automaton
14
+ end # module Stamina
15
+
@@ -0,0 +1,55 @@
1
+ require 'test/unit'
2
+ require 'stamina/stamina_test'
3
+ module Stamina
4
+ class Automaton
5
+ module Minimize
6
+ class MinimizeTest < StaminaTest
7
+
8
+ # To be overriden
9
+ def algo
10
+ nil
11
+ end
12
+
13
+ def test_on_unknown_1
14
+ return unless algo
15
+ dfa = load_adl_automaton("unknown_1.adl", __FILE__)
16
+ min = load_adl_automaton("unknown_1.min.adl", __FILE__)
17
+ assert_equivalent(algo.execute(dfa), min)
18
+ end
19
+
20
+ # From slide 10 in http://www.clear.rice.edu/comp412/Lectures/L07DFAMin-1up.pdf
21
+ def test_on_rice_edu_10
22
+ return unless algo
23
+ dfa = load_adl_automaton("rice_edu_10.adl", __FILE__)
24
+ min = load_adl_automaton("rice_edu_10.min.adl", __FILE__)
25
+ assert_equivalent(algo.execute(dfa), min)
26
+ end
27
+
28
+ # From slide 13 in http://www.clear.rice.edu/comp412/Lectures/L07DFAMin-1up.pdf
29
+ def test_on_rice_edu_13
30
+ return unless algo
31
+ dfa = load_adl_automaton("rice_edu_13.adl", __FILE__)
32
+ min = load_adl_automaton("rice_edu_13.min.adl", __FILE__)
33
+ assert_equivalent(algo.execute(dfa), min)
34
+ end
35
+
36
+ def test_it_strips_when_needed
37
+ return unless algo
38
+ dfa = load_adl_automaton("should_strip_1.adl", __FILE__)
39
+ min = load_adl_automaton("should_strip_1.min.adl", __FILE__)
40
+ assert_equivalent(algo.execute(dfa), min)
41
+ end
42
+
43
+ def test_it_has_no_effect_on_already_minimal
44
+ return unless algo
45
+ dfa = load_adl_automaton("rice_edu_13.min.adl", __FILE__)
46
+ min = algo.execute(dfa)
47
+ assert_equal dfa.complete.state_count, min.complete.state_count
48
+ assert_equivalent(min, dfa)
49
+ end
50
+
51
+ end # class MinimizeTest
52
+ end # module Minimize
53
+ end # class Automaton
54
+ end # module Stamina
55
+