feldtruby 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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