feldtruby 0.2.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 (46) hide show
  1. data/.autotest +23 -0
  2. data/.gemtest +0 -0
  3. data/History.txt +4 -0
  4. data/Manifest.txt +44 -0
  5. data/README.md +63 -0
  6. data/README.txt +59 -0
  7. data/Rakefile +19 -0
  8. data/TODO +6 -0
  9. data/lib/feldtruby/array/basic_stats.rb +88 -0
  10. data/lib/feldtruby/array/count_by.rb +7 -0
  11. data/lib/feldtruby/array.rb +34 -0
  12. data/lib/feldtruby/file/file_change_watcher.rb +88 -0
  13. data/lib/feldtruby/file/tempfile.rb +25 -0
  14. data/lib/feldtruby/float.rb +17 -0
  15. data/lib/feldtruby/math/rand.rb +5 -0
  16. data/lib/feldtruby/net/html_doc_getter.rb +31 -0
  17. data/lib/feldtruby/optimize/differential_evolution.rb +186 -0
  18. data/lib/feldtruby/optimize/max_steps_termination_criterion.rb +24 -0
  19. data/lib/feldtruby/optimize/objective.rb +302 -0
  20. data/lib/feldtruby/optimize/optimizer.rb +145 -0
  21. data/lib/feldtruby/optimize/random_search.rb +9 -0
  22. data/lib/feldtruby/optimize/search_space.rb +69 -0
  23. data/lib/feldtruby/optimize/stdout_logger.rb +138 -0
  24. data/lib/feldtruby/optimize.rb +28 -0
  25. data/lib/feldtruby/string/to_iso.rb +7 -0
  26. data/lib/feldtruby/time.rb +22 -0
  27. data/lib/feldtruby/vector.rb +14 -0
  28. data/lib/feldtruby/visualization/circos.rb +25 -0
  29. data/lib/feldtruby/word_counter.rb +100 -0
  30. data/lib/feldtruby.rb +6 -0
  31. data/test/helper.rb +7 -0
  32. data/test/test_array.rb +71 -0
  33. data/test/test_array_basic_stats.rb +130 -0
  34. data/test/test_array_count_by.rb +13 -0
  35. data/test/test_float.rb +20 -0
  36. data/test/test_html_doc_getter.rb +16 -0
  37. data/test/test_optimize.rb +55 -0
  38. data/test/test_optimize_differential_evolution.rb +42 -0
  39. data/test/test_optimize_objective.rb +157 -0
  40. data/test/test_optimize_populationbasedoptimizer.rb +24 -0
  41. data/test/test_optimize_random_search.rb +46 -0
  42. data/test/test_optimize_search_space.rb +97 -0
  43. data/test/test_time.rb +27 -0
  44. data/test/test_vector.rb +98 -0
  45. data/test/test_word_counter.rb +57 -0
  46. metadata +149 -0
@@ -0,0 +1,55 @@
1
+ require 'feldtruby/optimize'
2
+
3
+ class TestOptimize < MiniTest::Unit::TestCase
4
+ def test_rosenbrock_optimization_as_in_README
5
+ xbest, ybest = FeldtRuby::Optimize.optimize(0, 2, {:verbose => false}) {|x, y|
6
+ (1 - x)**2 + 100*(y - x*x)**2
7
+ }
8
+ assert_in_delta 1.0, xbest
9
+ assert_in_delta 1.0, ybest
10
+ end
11
+
12
+ def in_vicinity?(x, y, delta = 0.01)
13
+ (x-y).abs < delta
14
+ end
15
+
16
+ def test_himmelsblau_minimization
17
+ # For details see: http://en.wikipedia.org/wiki/Himmelblau%27s_function
18
+ xbest, ybest = FeldtRuby::Optimize.minimize(-5, 5, {:maxNumSteps => 5000, :verbose => false}) {|x, y|
19
+ (x*x + y - 11)**2 + (x + y*y + - 7)**2
20
+ }
21
+
22
+ # There are 4 local minima:
23
+ # f( 3.000000, 2.000000) = 0.0
24
+ # f(-2.805118, 3.131312) = 0.0
25
+ # f(-3.779310, -3.283186) = 0.0
26
+ # f( 3.584428, -1.848126) = 0.0
27
+ # and it is unlikely that we are not in the vicinity of one of those after optimization.
28
+
29
+ if in_vicinity?(xbest, 3.000000)
30
+ assert_in_delta 3.000000, xbest, 0.1
31
+ assert_in_delta 2.000000, ybest, 0.1
32
+ elsif in_vicinity?(xbest, -2.805118)
33
+ assert_in_delta -2.805118, xbest, 0.1
34
+ assert_in_delta 3.131312, ybest, 0.1
35
+ elsif in_vicinity?(xbest, -3.779310)
36
+ assert_in_delta -3.779310, xbest, 0.1
37
+ assert_in_delta -3.283186, ybest, 0.1
38
+ elsif in_vicinity?(xbest, 3.584428)
39
+ assert_in_delta 3.584428, xbest, 0.1
40
+ assert_in_delta -1.848126, ybest, 0.1
41
+ else
42
+ assert false, "Solution [#{xbest}, #{ybest}] is not close to any minima"
43
+ end
44
+ end
45
+
46
+ def test_himmelsblau_maximization
47
+ # There is a local maxima that can be found if we search in a smaller box around 0.0.
48
+ # For details see: http://en.wikipedia.org/wiki/Himmelblau%27s_function
49
+ xbest, ybest = FeldtRuby::Optimize.maximize(-1, 1, {:maxNumSteps => 2000, :verbose => false}) {|x, y|
50
+ (x*x + y - 11)**2 + (x + y*y + - 7)**2
51
+ }
52
+ assert_in_delta -0.270845, xbest, 0.1
53
+ assert_in_delta -0.923039, ybest, 0.1
54
+ end
55
+ end
@@ -0,0 +1,42 @@
1
+ require 'feldtruby/optimize/differential_evolution'
2
+ require 'feldtruby/array/basic_stats'
3
+
4
+ class MinimizeRMS < FeldtRuby::Optimize::Objective
5
+ def objective_min_rms(candidate)
6
+ candidate.rms
7
+ end
8
+ end
9
+
10
+ class MinimizeRMSAndSum < MinimizeRMS
11
+ def objective_min_sum(candidate)
12
+ candidate.sum.abs
13
+ end
14
+ end
15
+
16
+ class TestDifferentialEvolution < MiniTest::Unit::TestCase
17
+ include FeldtRuby::Optimize
18
+ def setup
19
+ @s2 = SearchSpace.new_symmetric(2, 1)
20
+ @s4 = SearchSpace.new_symmetric(4, 1)
21
+
22
+ @o1 = MinimizeRMS.new
23
+ @o2 = MinimizeRMSAndSum.new
24
+
25
+ @de1 = DEOptimizer.new(@o1, @s2, {:verbose => false, :maxNumSteps => 1000})
26
+ @de2 = DEOptimizer.new(@o2, @s4, {:verbose => false, :maxNumSteps => 1234})
27
+ end
28
+
29
+ def test_de_for_small_vector_with_rms
30
+ @de1.optimize()
31
+ # Very unlikely we get a number over 0.30 (2 elements) after 1000 steps...
32
+ assert @de1.best.sum <= 0.40
33
+ assert_equal 1000, @de1.num_optimization_steps
34
+ end
35
+
36
+ def test_de_for_small_vector_with_rms_and_sum_for_more_steps
37
+ @de2.optimize()
38
+ # Very unlikely we get a number over 0.40 (4 elements)...
39
+ assert @de2.best.sum <= 0.40
40
+ assert_equal 1234, @de2.num_optimization_steps
41
+ end
42
+ end
@@ -0,0 +1,157 @@
1
+ require 'feldtruby/optimize/objective'
2
+ require 'feldtruby/array'
3
+
4
+ require 'pp'
5
+
6
+ class SingleObjective1 < FeldtRuby::Optimize::Objective
7
+ # Sum of candidate vector of values should be small
8
+ def objective_min_small_sum(candidate)
9
+ candidate.sum
10
+ end
11
+ end
12
+
13
+ class TestSingleObjective < MiniTest::Unit::TestCase
14
+ def setup
15
+ @o = SingleObjective1.new
16
+ end
17
+
18
+ def test_has_one_aspect
19
+ assert_equal 1, @o.num_aspects
20
+ end
21
+
22
+ def test_quality_value
23
+ assert_equal 1, @o.quality_value([1])
24
+ assert_equal 3, @o.quality_value([1, 2])
25
+ assert_equal( -42, @o.quality_value([1, 2, -45]) )
26
+ end
27
+ end
28
+
29
+ class TwoMinObjectives1 < FeldtRuby::Optimize::Objective
30
+ def objective_min_distance_between(candidate)
31
+ candidate.distance_between_elements.sum
32
+ end
33
+ def objective_min_sum(candidate)
34
+ candidate.sum
35
+ end
36
+ end
37
+
38
+ class TestTwoObjectives < MiniTest::Unit::TestCase
39
+ def setup
40
+ @o = TwoMinObjectives1.new
41
+ end
42
+ def test_has_two_aspects
43
+ assert_equal 2, @o.num_aspects
44
+ end
45
+ def test_global_min_values_per_aspect
46
+ assert_equal [Float::INFINITY, Float::INFINITY], @o.global_min_values_per_aspect
47
+ end
48
+ def test_global_max_values_per_aspect
49
+ assert_equal [-Float::INFINITY, -Float::INFINITY], @o.global_max_values_per_aspect
50
+ end
51
+ def test_update_global_mins_and_maxs
52
+ @o.update_global_mins_and_maxs([1,2])
53
+ assert_equal [1,2], @o.global_min_values_per_aspect
54
+ assert_equal [1,2], @o.global_max_values_per_aspect
55
+
56
+ @o.update_global_mins_and_maxs([1,3])
57
+ assert_equal [1,2], @o.global_min_values_per_aspect
58
+ assert_equal [1,3], @o.global_max_values_per_aspect
59
+
60
+ @o.update_global_mins_and_maxs([0,8])
61
+ assert_equal [0,2], @o.global_min_values_per_aspect
62
+ assert_equal [1,8], @o.global_max_values_per_aspect
63
+ end
64
+ def test_sub_objective_values
65
+ assert_equal [1,3], @o.sub_objective_values([1,2])
66
+ assert_equal [3,7], @o.sub_objective_values([1,2,4])
67
+ assert_equal [4,8], @o.sub_objective_values([1,2,5])
68
+ end
69
+ def test_qv_mwgr
70
+ @o.update_global_mins_and_maxs([0, 0])
71
+ @o.update_global_mins_and_maxs([1, 3])
72
+ assert_equal 0.0, @o.qv_mwgr([1,2])
73
+ assert_equal 1.0, @o.qv_mwgr([0,0])
74
+ end
75
+ def test_qv_mwgr_complex
76
+ # Set first values => fitness is always zero
77
+ assert_equal 0.0, @o.qv_mwgr([1,2,3])
78
+ # Now we come with a worse candidate => still zero
79
+ assert_equal 0.0, @o.qv_mwgr([1,2,5])
80
+ # But now the previous value is the best candidate we have seen so gets maximum quality value, 2 aspects * 1.0 per aspect
81
+ assert_equal 1.0, @o.qv_mwgr([1,2,3])
82
+ # The previous worst is still the worst
83
+ assert_equal 0.0, @o.qv_mwgr([1,2,5])
84
+ # And now some complex ones that are between the prev best and worst
85
+ assert_equal( ((4.0 - 3.0)/(4-2) + (8.0 - 7)/(8-6))/2, @o.qv_mwgr([1,2,4]) )
86
+ assert_equal( ((4.0 - 3.5)/(4-2) + (8.0 - 7.5)/(8-6))/2, @o.qv_mwgr([1,2,4.5]) )
87
+ # Now extend the global best with a new best
88
+ assert_equal 1.0, @o.qv_mwgr([1,2,2]) # new global min = [1, 5] and max the same at [4, 8]
89
+ # And the in between candidates now have new values based on the new mins
90
+ assert_equal( ((4.0 - 3.0)/(4-1) + (8.0 - 7)/(8-5))/2, @o.qv_mwgr([1,2,4]) )
91
+ assert_equal( ((4.0 - 3.5)/(4-1) + (8.0 - 7.5)/(8-5))/2, @o.qv_mwgr([1,2,4.5]) )
92
+ end
93
+ end
94
+
95
+ describe "Objective" do
96
+ before do
97
+ @o = SingleObjective1.new
98
+ @o2 = TwoMinObjectives1.new
99
+ @c = [1,2,3]
100
+ end
101
+
102
+ it "attaches quality value to an evaluated object" do
103
+ qv = @o.quality_value(@c)
104
+ @c._quality_value.must_equal qv
105
+ @c._objective.must_equal @o
106
+ end
107
+
108
+ it "overwrites quality value if evaluated again with another objective" do
109
+ @o.quality_value(@c)
110
+ qv2 = @o2.quality_value(@c)
111
+ @c._quality_value.must_equal qv2
112
+ @c._objective.must_equal @o2
113
+ end
114
+
115
+ it "is re-evaluated if the objective has changed since original evaluation" do
116
+ qv = @o2.quality_value(@c)
117
+ @o2.quality_value([1,2,3,4,5]) # Higher sum so max updated
118
+ qvnew = @c._quality_value
119
+ qvnew.wont_equal qv
120
+ end
121
+
122
+ describe "objects that have not been evaluated" do
123
+ it "has not attached quality values" do
124
+ c = [1,2,3]
125
+ c._quality_value.must_equal nil
126
+ end
127
+ end
128
+
129
+ describe "version numbers" do
130
+ it "has version number 0 when no evaluation has taken place" do
131
+ @o.current_version.must_equal 0
132
+ @o2.current_version.must_equal 0
133
+ end
134
+
135
+ it "never changes the version number for a single objective since ratios are not used" do
136
+ @o.quality_value([1])
137
+ @o.current_version.must_equal 0
138
+ end
139
+
140
+ it "increases the version number each time a quality aspect of a candidate is more extreme than previously seen (when multi-objective)" do
141
+ @o2.quality_value([1])
142
+ @o2.current_version.must_equal 4 # Both min and max changed for two objectives => 2*2
143
+ @o2.quality_value([2])
144
+ @o2.current_version.must_equal 5 # New max values for sum objective => +1
145
+ @o2.quality_value([1,2])
146
+ @o2.current_version.must_equal 7 # New max values for both objectives => +2
147
+ @o2.quality_value([0])
148
+ @o2.current_version.must_equal 8 # New min value for sum objective => +1
149
+ @o2.quality_value([-1])
150
+ @o2.current_version.must_equal 9 # New min value for sum objective => +1
151
+ @o2.quality_value([-2])
152
+ @o2.current_version.must_equal 10 # New min value for sum objective => +1
153
+ @o2.quality_value([1,2,3])
154
+ @o2.current_version.must_equal 12 # New max for both objectives => +1
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,24 @@
1
+ require 'feldtruby/optimize/optimizer'
2
+
3
+ class TestPopulationBasedOptimizer < MiniTest::Unit::TestCase
4
+ def setup
5
+ @o1 = MinimizeRMS.new
6
+ @pbo1 = FeldtRuby::Optimize::PopulationBasedOptimizer.new(@o1)
7
+ end
8
+
9
+ def test_population_size
10
+ assert_equal 100, @pbo1.population_size
11
+ end
12
+
13
+ def test_sample_population_indices_without_replacement
14
+ 100.times do
15
+ num_samples = rand_int(@pbo1.population_size)
16
+ sampled_indices = @pbo1.sample_population_indices_without_replacement(num_samples)
17
+ assert_equal num_samples, sampled_indices.length
18
+ assert_equal num_samples, sampled_indices.uniq.length, "Some elements where the same in #{sampled_indices.inspect}"
19
+ sampled_indices.each do |i|
20
+ assert i >= 0 && i < @pbo1.population_size
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,46 @@
1
+ require 'feldtruby/optimize/random_search'
2
+ require 'feldtruby/array/basic_stats'
3
+
4
+
5
+ unless defined?(MinimizeRMS)
6
+ class MinimizeRMS < FeldtRuby::Optimize::Objective
7
+ def objective_min_rms(candidate)
8
+ candidate.rms
9
+ end
10
+ end
11
+ end
12
+
13
+ unless defined?(MinimizeRMSAndSum)
14
+ class MinimizeRMSAndSum < MinimizeRMS
15
+ def objective_min_sum(candidate)
16
+ candidate.sum.abs
17
+ end
18
+ end
19
+ end
20
+
21
+ class TestRandomSearcher < MiniTest::Unit::TestCase
22
+ def setup
23
+ @s2 = FeldtRuby::Optimize::SearchSpace.new_symmetric(2, 1)
24
+ @s4 = FeldtRuby::Optimize::SearchSpace.new_symmetric(4, 1)
25
+
26
+ @o1 = MinimizeRMS.new
27
+ @o2 = MinimizeRMSAndSum.new
28
+
29
+ @rs1 = FeldtRuby::Optimize::RandomSearcher.new(@o1, @s2, {:verbose => false, :maxNumSteps => 1000})
30
+ @rs2 = FeldtRuby::Optimize::RandomSearcher.new(@o2, @s4, {:verbose => false, :maxNumSteps => 2187})
31
+ end
32
+
33
+ def test_random_search_for_small_vector_with_rms
34
+ @rs1.optimize()
35
+ # Very unlikely we get a number over 0.40 (2 elements) after 1000 steps...
36
+ assert @rs1.best.sum <= 0.40
37
+ assert_equal 1000, @rs1.num_optimization_steps
38
+ end
39
+
40
+ def test_random_search_for_small_vector_with_rms_and_sum_for_more_steps
41
+ @rs2.optimize()
42
+ # Very unlikely we get a number over 0.40 (3 elements)...
43
+ assert @rs2.best.sum <= 0.60
44
+ assert_equal 2187, @rs2.num_optimization_steps
45
+ end
46
+ end
@@ -0,0 +1,97 @@
1
+ require 'minitest/spec'
2
+ require 'feldtruby/optimize/search_space'
3
+
4
+ describe "SearchSpace#bound" do
5
+ before do
6
+ @sp = FeldtRuby::Optimize::SearchSpace.new([-5, -3], [5, 7])
7
+ end
8
+
9
+ it "returns the values if they are INSIDE the search space boundaries" do
10
+ @sp.bound([-1, 0]).must_equal [-1, 0]
11
+ end
12
+
13
+ it "returns the values if they are ON the search space boundaries" do
14
+ @sp.bound([-5, -3]).must_equal [-5, -3]
15
+ @sp.bound([5, 7]).must_equal [5, 7]
16
+ @sp.bound([-5, 7]).must_equal [-5, 7]
17
+ @sp.bound([5, -3]).must_equal [5, -3]
18
+ end
19
+
20
+ it "generates a value INSIDE the search space boundaries when a value is given that is outside (negative, outside on one dimension)" do
21
+ l, h = @sp.bound([-10, 3.4])
22
+ h.must_equal 3.4
23
+ l.must_be :>=, -5
24
+ l.must_be :<=, 5
25
+
26
+ l, h = @sp.bound([-4.6, -4.1])
27
+ l.must_equal(-4.6)
28
+ h.must_be :>=, -3
29
+ h.must_be :<=, 7
30
+ end
31
+
32
+ it "generates a value INSIDE the search space boundaries when a value is given that is outside (positive, outside on one dimension)" do
33
+ l, h = @sp.bound([6, 2.7])
34
+ h.must_equal 2.7
35
+ l.must_be :>=, -5
36
+ l.must_be :<=, 5
37
+
38
+ l, h = @sp.bound([-4.6, 8.4])
39
+ l.must_equal(-4.6)
40
+ h.must_be :>=, -3
41
+ h.must_be :<=, 7
42
+ end
43
+
44
+ it "generates a value INSIDE the search space boundaries when a value is given that is outside (positive, outside on one dimension)" do
45
+ l, h = @sp.bound([-60.2, 1])
46
+ l.must_be :>=, -5
47
+ l.must_be :<=, 5
48
+ h.must_be :>=, -3
49
+ h.must_be :<=, 7
50
+ end
51
+ end
52
+
53
+ class TestSearchSpace < MiniTest::Unit::TestCase
54
+ def setup
55
+ @s1 = FeldtRuby::Optimize::SearchSpace.new([-5], [5])
56
+ @s2 = FeldtRuby::Optimize::SearchSpace.new([-1, -1], [1, 1])
57
+ @s3 = FeldtRuby::Optimize::SearchSpace.new([-1, -5, -100], [1, 50, 1000])
58
+ @s4 = FeldtRuby::Optimize::SearchSpace.new_symmetric(4, 10)
59
+ end
60
+
61
+ def test_num_variables
62
+ assert_equal 1, @s1.num_variables
63
+ assert_equal 2, @s2.num_variables
64
+ assert_equal 3, @s3.num_variables
65
+ assert_equal 4, @s4.num_variables
66
+ end
67
+
68
+ def assert_gen_candidate_and_is_candidate(ss, numRepetitions = 100)
69
+ numRepetitions.times do
70
+ c = ss.gen_candidate()
71
+ assert_equal ss.num_variables, c.length
72
+ c.length.times do |i|
73
+ assert ss.min_values[i] <= c[i]
74
+ assert ss.max_values[i] >= c[i]
75
+ end
76
+ assert ss.is_candidate?(c)
77
+ end
78
+ end
79
+
80
+ def test_gen_candidate_and_is_candidate
81
+ assert_gen_candidate_and_is_candidate(@s1)
82
+ assert_gen_candidate_and_is_candidate(@s2)
83
+ assert_gen_candidate_and_is_candidate(@s3)
84
+ assert_gen_candidate_and_is_candidate(@s4)
85
+ end
86
+
87
+ def test_new_from_min_max
88
+ ss = FeldtRuby::Optimize::SearchSpace.new_from_min_max(2, -7, 2)
89
+ assert_equal 2, ss.num_variables
90
+ end
91
+
92
+ def test_bound_returns_vector_if_supplied_a_vector
93
+ s1 = FeldtRuby::Optimize::SearchSpace.new([-5, -3], [5, 7])
94
+ b = s1.bound(Vector[-10, 5])
95
+ assert Vector, b.class
96
+ end
97
+ end
data/test/test_time.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'feldtruby/time'
2
+
3
+ class TestFeldtRubyTime < MiniTest::Unit::TestCase
4
+ def test_timestamp_short
5
+ str = Time.timestamp({:short => true})
6
+ assert_equal 15, str.length
7
+ assert str =~ /^\d{6} \d{2}:\d{2}\.\d{2}/
8
+ end
9
+
10
+ def test_timestamp_long
11
+ str = Time.timestamp()
12
+ assert_equal 17, str.length
13
+ assert str =~ /^\d{8} \d{2}:\d{2}\.\d{2}/
14
+ end
15
+
16
+ def test_human_readable_timestr
17
+ assert_equal "74.92usec", Time.human_readable_timestr(0.000074916)
18
+ assert_equal "0.75msec", Time.human_readable_timestr(0.00074916)
19
+ assert_equal "7.49msec", Time.human_readable_timestr(0.0074916)
20
+ assert_equal "74.92msec", Time.human_readable_timestr(0.074916)
21
+ assert_equal "0.75sec", Time.human_readable_timestr(0.74916)
22
+ assert_equal "7.49sec", Time.human_readable_timestr(7.4916)
23
+ assert_equal "1.25mins", Time.human_readable_timestr(74.916)
24
+ assert_equal "12.49mins", Time.human_readable_timestr(749.16)
25
+ assert_equal "2.08hours", Time.human_readable_timestr(7491.6)
26
+ end
27
+ end
@@ -0,0 +1,98 @@
1
+ require 'helper'
2
+ require 'feldtruby/vector'
3
+
4
+ class TestVectorBasicStats < MiniTest::Unit::TestCase
5
+ def test_sum_normal
6
+ assert_equal 3, Vector[1,2].sum
7
+ assert_equal 6, Vector[1,2,3].sum
8
+ end
9
+
10
+ def test_sum_one_element
11
+ assert_equal 1, Vector[1].sum
12
+ end
13
+
14
+ def test_sum_empty_array
15
+ assert_equal 0, Vector[].sum
16
+ end
17
+
18
+ def test_mean_normal
19
+ assert_equal 1.5, Vector[1,2].mean
20
+ assert_equal 2, Vector[1,2,3].mean
21
+ end
22
+
23
+ def test_mean_one_element
24
+ assert_equal 1, Vector[1].mean
25
+ end
26
+
27
+ def test_mean_empty_array
28
+ assert_equal 0, Vector[].mean
29
+ end
30
+
31
+ def test_mean_from_wikipedia_def_page_for_stdev
32
+ assert_equal 2.0, Vector[2, 4, 4, 4, 5, 5, 7, 9].stdev
33
+ end
34
+
35
+ def test_root_mean_square
36
+ assert_equal Math.sqrt((1*1 + 2*2)/2.0), Vector[1, 2].root_mean_square
37
+ assert_equal Math.sqrt((10*10 + 243*243)/2.0), Vector[10, 243].rms
38
+ end
39
+
40
+ def test_weighted_sum_one_element
41
+ assert_equal 1, Vector[1].weighted_sum([1])
42
+ assert_equal 2, Vector[1].weighted_sum([2])
43
+ end
44
+
45
+ def test_weighted_sum_two_elements
46
+ assert_equal 3, Vector[1, 2].weighted_sum([1, 1])
47
+ assert_equal 22, Vector[1, 4].weighted_sum([2, 5])
48
+ end
49
+
50
+ def test_weighted_mean_one_elements
51
+ assert_equal 1, Vector[1].weighted_mean([1])
52
+ assert_equal 4, Vector[4].weighted_mean([2])
53
+ end
54
+
55
+ def test_weighted_mean_two_elements
56
+ assert_equal 1.5, Vector[1, 2].weighted_mean([1, 1])
57
+ assert_equal 22.0/7, Vector[1, 4].weighted_mean([2, 5])
58
+
59
+ assert_equal 1.5, Vector[1, 2].weighted_mean()
60
+ end
61
+ end
62
+
63
+ describe Vector do
64
+ describe "Slicing a vector" do
65
+ before do
66
+ @v = Vector[1,2,3,4,5]
67
+ end
68
+
69
+ it "does not mess up normal indexing" do
70
+ @v[0].must_equal 1
71
+ @v[1].must_equal 2
72
+ @v[2].must_equal 3
73
+ @v[3].must_equal 4
74
+ @v[4].must_equal 5
75
+ end
76
+
77
+ it "works in the middle of a vector" do
78
+ @v[1,1].must_equal Vector[2]
79
+ @v[1,2].must_equal Vector[2,3]
80
+ @v[2,3].must_equal Vector[3,4,5]
81
+ end
82
+
83
+ it "works at the start of a vector" do
84
+ @v[0,1].must_equal Vector[1]
85
+ @v[0,2].must_equal Vector[1,2]
86
+ @v[0,5].must_equal Vector[1,2,3,4,5]
87
+ end
88
+
89
+ it "works at the end of a vector" do
90
+ @v[4,1].must_equal Vector[5]
91
+ end
92
+
93
+ it "works even if goes past the vector" do
94
+ @v[4,2].must_equal Vector[5]
95
+ @v[4,10].must_equal Vector[5]
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,57 @@
1
+ require 'minitest/spec'
2
+ require 'feldtruby/word_counter'
3
+
4
+ describe "WordCounter" do
5
+ it "can count words" do
6
+ wc = FeldtRuby::WordCounter.new
7
+ wc.count_words "The fox likes running. The fox likes it. It feels good for the fox."
8
+ wc.words.sort.must_equal ["feels", "fox", "good", "likes", "running"]
9
+ wc.count("feels").must_equal 1
10
+ wc.count("fox").must_equal 3
11
+ wc.count("good").must_equal 1
12
+ wc.count("likes").must_equal 2
13
+ wc.count("running").must_equal 1
14
+
15
+ wc.count("notinthere").must_equal 0
16
+ end
17
+
18
+ it "can return a top list of most common words" do
19
+ wc = FeldtRuby::WordCounter.new
20
+ wc.count_words "The fox likes running. The fox likes it. It feels good for the fox."
21
+ t = wc.top_words(1)
22
+ t.must_be_instance_of Array
23
+ t.must_equal [["fox", 3]]
24
+ wc.top_words(2).must_equal [["fox", 3], ["likes", 2]]
25
+ end
26
+
27
+ it "can merge words that are very close to each other (singularis/pluralis/-ing)" do
28
+ wc = FeldtRuby::WordCounter.new
29
+ wc.count_words "test tests testing testing program programs programs design"
30
+ wc.merge!
31
+
32
+ wc.count("test|tests|testing").must_equal 4
33
+ wc.count("program|programs").must_equal 3
34
+ wc.count("design").must_equal 1
35
+ end
36
+
37
+ it "has merged word descriptions in the top list" do
38
+ wc = FeldtRuby::WordCounter.new
39
+ wc.count_words "test tests testing testing program programs programs"
40
+ wc.merge!
41
+
42
+ wc.top_words(2).must_equal [["test|tests|testing", 4], ["program|programs", 3]]
43
+ end
44
+ end
45
+
46
+ describe "NgramWordCounter" do
47
+ it "counts all 2-grams" do
48
+ wc = FeldtRuby::NgramWordCounter.new(2)
49
+ wc.count_words "The fox likes running. The fox likes it. It feels good for the fox."
50
+
51
+ wc.words.sort.must_equal ["fox likes", "likes running", "feels good"].sort
52
+
53
+ wc.count("fox likes").must_equal 2
54
+ wc.count("likes running").must_equal 1
55
+ wc.count("feels good").must_equal 1
56
+ end
57
+ end