feldtruby 0.4.9 → 0.4.10

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.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- feldtruby (0.4.8)
4
+ feldtruby (0.4.9)
5
5
  bson_ext
6
6
  json
7
7
  mongo
@@ -0,0 +1,189 @@
1
+ require 'feldtruby/optimize'
2
+ require 'feldtruby/logger'
3
+
4
+ module FeldtRuby::Optimize
5
+
6
+ # An archive keeps a record of all "interesting" candidate solutions found
7
+ # during optimization. It has two purposes: to contribute to the optimization
8
+ # itself (since seeding a search with previously "good" solutions can improve
9
+ # convergence speed) but also to provide a current view of the best and most
10
+ # interesting solutions found.
11
+ #
12
+ # Interestingness is hard to define but we posit that two elements are always
13
+ # important:
14
+ # 1. Being good, i.e. having good values for one or many of the sub-objectives
15
+ # 2. Being different, i.e. behaving differently (phenotypic diversity) or
16
+ # looking different (genotypic diversity), than other candidate solutions.
17
+ #
18
+ # Different types of optimization can require different trade-offs between
19
+ # these elements but all archives should support both types inherently.
20
+ # The default choices for the main class is to use the quality calculated by the
21
+ # objective class to judge "being good" and to use a diversity objective to
22
+ # judge "being different".
23
+ #
24
+ # We also posit that it is not enough only to be diverse (although there is
25
+ # some research showing that novelty search in itself may be as good/important
26
+ # as directed search) so the default behavior is to only add a solution to the
27
+ # diversity top lists if it is within a certain percentage of the best solutions
28
+ # on the other (being good) top list. Thus the basic design is to keep one top
29
+ # list per fitness goal, one overall aggregate fitness top list and then one
30
+ # top list per diversity goal.
31
+ #
32
+ # We note that diversity is just another type of objective (although a relative
33
+ # rather than an absolute one) and we can use the existing
34
+ # classes to implement that. A default diversity objective looks at genotypic
35
+ # and fitness diversity by default but more elaborate schemes can be used.
36
+ #
37
+ # One way to make this model easier to understand is to call the three types:
38
+ # generalists (overall good, aggregated fitness best)
39
+ # specialists (doing one thing very, very good, sub-objective fitness best)
40
+ # weirdos (different but with clear qualitites, ok but diverse)
41
+ class Archive
42
+ #include FeldtRuby::Logging
43
+
44
+ DefaultParams = {
45
+ :NumTopPerGoal => 5, # Number of solutions in top list per individual goal
46
+ :NumTopAggregate => 20, # Number of solutions in top list for aggregate quality
47
+
48
+ # Number of solutions in diversity top list. This often needs to
49
+ # be larger than the other top lists since there are "more" ways to be
50
+ # diverse than to be good... OTOH it costs CPU power to have large values
51
+ # here since the quality values needs to be recalculated whenever there is
52
+ # a new best. So we keep them small.
53
+ :NumTopDiversityAggregate => 10,
54
+
55
+ # Max percent distance (given as a ratio, i.e. 0.05 means 5%) from best
56
+ # solution (top of Aggregate list) that a solution is allowed to have
57
+ # to be allowed on the diversity list. If it is more than 5% from best
58
+ # we don't consider adding it to a diversity list.
59
+ :MaxPercentDistanceToBestForDiversity => 0.05
60
+ }
61
+
62
+ attr_reader :objective, :diversity_objective
63
+
64
+ attr_reader :specialists, :generalists, :weirdos
65
+
66
+ def initialize(fitnessObjective, diversityObjective, params = DefaultParams.clone)
67
+ @objective = fitnessObjective
68
+ self.diversity_objective = diversityObjective
69
+ @params = DefaultParams.clone.update(params)
70
+ init_top_lists
71
+ end
72
+
73
+ def diversity_objective=(diversityObjective)
74
+ @diversity_objective = diversityObjective
75
+ @diversity_objective.archive = self if @diversity_objective.respond_to?(:archive=)
76
+ @diversity_objective.quality_objective = @objective if @diversity_objective.respond_to?(:quality_objective=)
77
+ end
78
+
79
+ def best
80
+ # The top of the Generalists top list is the overall best
81
+ @generalists[0]
82
+ end
83
+
84
+ def init_top_lists
85
+ @specialists = Array.new
86
+ @objective.num_goals.times do |i|
87
+ @specialists << GoalTopList.new(@params[:NumTopPerGoal], @objective, i)
88
+ end
89
+
90
+ @generalists = GlobalTopList.new(@params[:NumTopAggregate], @objective)
91
+
92
+ @weirdos = GlobalTopList.new(@params[:NumTopDiversityAggregate], @diversity_objective)
93
+ end
94
+
95
+ # Add a candidate to the top lists if it is good enough to be there. Throws
96
+ # out worse candidates as appropriate.
97
+ def add(candidate)
98
+ @specialists.each {|tl| tl.add(candidate)}
99
+
100
+ # Detect if we get a new best one by saving the previous best.
101
+ prev_best = best
102
+ @generalists.add candidate
103
+
104
+ # If there was a new best we invalidate the diversity quality values since
105
+ # they are relative and must be recalculated. Note that this might incur
106
+ # a big penalty if there are frequent changes at the top.
107
+ if prev_best != best
108
+ # logger.log_data :new_best, {
109
+ # "New best" => best,
110
+ # "New quality" => @objective.quality_of(best),
111
+ # "Old best" => prev_best,
112
+ # "Old quality" => @objective.quality_of(prev_best)},
113
+ # "Archive: New best solution found", true
114
+
115
+ @weirdos.each {|w| @diversity_objective.invalidate_quality_of(w)}
116
+ elsif good_enough_quality_to_be_interesting?(candidate)
117
+ # When we add a new one this will lead to re-calc of the diversity quality
118
+ # of the previous ones if there has been a new best since last time.
119
+ @weirdos.add candidate
120
+ end
121
+ end
122
+
123
+ # Is quality of candidate good enough (within MaxPercentDistanceToBestForDiversity
124
+ # from quality of best candidate)
125
+ def good_enough_quality_to_be_interesting?(candidate)
126
+ qv_best = @objective.quality_of(best).value
127
+ ((qv_best - @objective.quality_of(candidate).value).abs / qv_best) <= @params[:MaxPercentDistanceToBestForDiversity]
128
+ end
129
+
130
+ # A top list is an array of a fixed size that saves the top candidates
131
+ # based on their (aggregate) quality values.
132
+ class GlobalTopList
133
+ def initialize(maxSize, objective)
134
+ @max_size =maxSize
135
+ @top_list = Array.new
136
+ @objective = objective
137
+ end
138
+ def length; @top_list.length; end
139
+
140
+ def each
141
+ @top_list.each {|e| yield(e)}
142
+ end
143
+
144
+ def [](index)
145
+ @top_list[index]
146
+ end
147
+
148
+ def add(candidate)
149
+ last = @top_list.last
150
+ #puts "In #{self},\nlast = #{last}, candidate = #{candidate}, top_list = #{@top_list}"
151
+ if @top_list.length < @max_size || last.nil? || is_better_than?(candidate, last)
152
+ @top_list.pop if @top_list.length >= @max_size
153
+ @top_list << candidate
154
+ @top_list = sort_top_list
155
+ end
156
+ #puts "top_list = #{@top_list}"
157
+ end
158
+
159
+ def is_better_than?(candidate1, candidate2)
160
+ @objective.is_better_than?(candidate1, candidate2)
161
+ end
162
+
163
+ def sort_top_list
164
+ @top_list.sort_by {|c| @objective.quality_of(c).value}
165
+ end
166
+
167
+ def inspect
168
+ self.class.inspect + @top_list.inspect
169
+ end
170
+ end
171
+
172
+ class GoalTopList < GlobalTopList
173
+ def initialize(maxSize, objective, goalIndex)
174
+ super(maxSize, objective)
175
+ @index = goalIndex
176
+ end
177
+ def is_better_than?(candidate1, candidate2)
178
+ @objective.is_better_than_for_goal?(@index, candidate1, candidate2)
179
+ end
180
+ def sort_top_list
181
+ @top_list.sort_by {|c|
182
+ qv = @objective.quality_of(c)
183
+ qv.sub_quality(@index, true) # We want the sub quality value posed as a minimization goal regardless of whether it is a min or max goal
184
+ }
185
+ end
186
+ end
187
+ end
188
+
189
+ end
@@ -70,11 +70,11 @@ class DEOptimizerBase < EvolutionaryOptimizer
70
70
  }, "DE (step #{@num_optimization_steps}): Trial vector was better than target vector"
71
71
  update_candidate_in_population(target_index, trial)
72
72
  feedback_on_trial_vs_target(trial, target, true)
73
+ [best]
73
74
  else
74
75
  feedback_on_trial_vs_target(trial, target, false)
76
+ []
75
77
  end
76
-
77
- [best]
78
78
  end
79
79
 
80
80
  #####################################
@@ -163,6 +163,13 @@ class Objective
163
163
 
164
164
  end
165
165
 
166
+ # Invalidate the current quality object to ensure it will be recalculated
167
+ # the next time quality_of is called with this candidate. This is needed
168
+ # when there is a new best candidate set in an Archive, for example.
169
+ def invalidate_quality_of(candidate)
170
+ update_quality_value_in_object candidate, nil
171
+ end
172
+
166
173
  # Rank candidates from best to worst. Candidates that have the same quality
167
174
  # are randomly ordered.
168
175
  def rank_candidates(candidates, weights = self.weights)
@@ -510,4 +517,21 @@ class ObjectiveMaximizeBlock < Objective
510
517
  end
511
518
  end
512
519
 
520
+ # A diversity objective often is a secondary goal and is measured relative
521
+ # to quality goals/objectives or relative to candidates in the archive.
522
+ # Thus it needs to have access to these other two objects.
523
+ class DiversityObjective < Objective
524
+ attr_accessor :archive
525
+ attr_accessor :quality_objective
526
+ end
527
+
528
+ # The standard diversity objective is to just use the Euclidean distance
529
+ # to the best candidate found so far.
530
+ class FeldtRuby::Optimize::EuclideanDistanceToBest < FeldtRuby::Optimize::DiversityObjective
531
+ # Euclidean distance to best candidate. Genotype diversity.
532
+ def goal_max_euclidean_distance_to_best(candidate)
533
+ candidate.rms_from(archive.best)
534
+ end
535
+ end
536
+
513
537
  end
@@ -2,6 +2,7 @@ require 'feldtruby/optimize'
2
2
  require 'feldtruby/optimize/objective'
3
3
  require 'feldtruby/optimize/search_space'
4
4
  require 'feldtruby/optimize/max_steps_termination_criterion'
5
+ require 'feldtruby/optimize/archive'
5
6
  require 'feldtruby/math/rand'
6
7
  require 'feldtruby/array'
7
8
  require 'feldtruby/logger'
@@ -13,14 +14,15 @@ module FeldtRuby::Optimize
13
14
  class Optimizer
14
15
  include FeldtRuby::Logging
15
16
 
16
- attr_reader :options, :objective, :search_space, :best, :best_quality_value
17
- attr_reader :best_sub_quality_values, :num_optimization_steps, :termination_criterion
17
+ attr_reader :options, :objective, :search_space, :archive
18
+ attr_reader :num_optimization_steps, :termination_criterion
18
19
 
19
20
  def initialize(objective, searchSpace = FeldtRuby::Optimize::DefaultSearchSpace, options = {})
20
- @best = nil # To avoid warnings if not set
21
21
  @objective, @search_space = objective, searchSpace
22
22
  @options = FeldtRuby::Optimize.override_default_options_with(options)
23
23
 
24
+ init_archive
25
+
24
26
  # Must setup logger before setting options since verbosity of logger is
25
27
  # an option!
26
28
  setup_logger_and_distribute_to_instance_variables(options)
@@ -28,6 +30,28 @@ class Optimizer
28
30
  initialize_options(@options)
29
31
  end
30
32
 
33
+ def init_archive
34
+ if @options[:archive]
35
+
36
+ @archive = @options[:archive]
37
+
38
+ else
39
+
40
+ if @options[:archiveDiversityObjective]
41
+ diversity_objective = @options[:archiveDiversityObjective]
42
+ else
43
+ diversity_objective = @options[:archiveDiversityObjectiveClass].new
44
+ end
45
+
46
+ @archive = @options[:archiveClass].new(@objective, diversity_objective)
47
+
48
+ end
49
+ end
50
+
51
+ def best
52
+ @archive.best
53
+ end
54
+
31
55
  def initialize_options(options)
32
56
  self.logger.verbose = options[:verbose]
33
57
  @termination_criterion = options[:terminationCriterion]
@@ -37,15 +61,18 @@ class Optimizer
37
61
  def optimize()
38
62
  logger.log "Optimization with optimizer #{self.class.inspect} started"
39
63
  @num_optimization_steps = 0
40
- # Set up a random best since other methods require it
41
- update_best([search_space.gen_candidate()])
64
+
65
+ # Set up a random best for now that we can later compare to.
66
+ update_archive [search_space.gen_candidate()]
67
+
42
68
  begin
43
69
  while !termination_criterion.terminate?(self)
44
70
  new_candidates = optimization_step()
45
71
  @num_optimization_steps += 1
46
- update_best(new_candidates)
72
+ update_archive new_candidates
47
73
  end
48
74
  rescue Exception => e
75
+ puts e.inspect
49
76
  logger.log_data :exception, {
50
77
  :exception_class => e.class.inspect,
51
78
  :backtrace => e.backtrace.join("\n")
@@ -53,16 +80,18 @@ class Optimizer
53
80
  ensure
54
81
  logger.log "!!! - Optimization FINISHED after #{@num_optimization_steps} steps - !!!"
55
82
  end
83
+
56
84
  @objective.note_end_of_optimization(self)
57
85
  log_end_of_optimization
58
- @best # return the best
86
+
87
+ archive.best # return the best
59
88
  end
60
89
 
61
90
  def log_end_of_optimization
62
91
  logger.log("End of optimization\n" +
63
92
  " Optimizer: #{self.class}\n" +
64
- " Best found: #{@best}\n" +
65
- " Quality of best: #{@objective.quality_of(@best)}\n" +
93
+ " Best found: #{@archive.best}\n" +
94
+ " Quality of best: #{@objective.quality_of(@archive.best)}\n" +
66
95
  " Time used = #{Time.human_readable_timestr(logger.elapsed_time)}, " +
67
96
  "Steps performed = #{@num_optimization_steps}, " +
68
97
  "#{Time.human_readable_timestr(time_per_step, true)}/step")
@@ -77,21 +106,10 @@ class Optimizer
77
106
  def optimization_step()
78
107
  end
79
108
 
80
- # Update the best if a new best was found.
81
- def update_best(candidates)
82
- best_new, rest = objective.rank_candidates(candidates)
83
- if @best.nil? || @objective.is_better_than?(best_new, @best)
84
- qb = @best.nil? ? nil : @objective.quality_of(@best)
85
- logger.log_data :new_best, {
86
- "New best" => best_new,
87
- "New quality" => @objective.quality_of(best_new),
88
- "Old best" => @best,
89
- "Old quality" => qb}, "Optimizer (step #{@num_optimization_steps}): New best solution found", true
90
- @best = best_new
91
- true
92
- else
93
- false
94
- end
109
+ # Update the archive with newly found candidates. Array of candidates may be empty if
110
+ # no new candidates found.
111
+ def update_archive(candidates)
112
+ candidates.each {|c| @archive.add(c)}
95
113
  end
96
114
  end
97
115
 
@@ -211,7 +229,11 @@ DefaultOptimizationOptions = {
211
229
  :verbose => true,
212
230
  :populationSize => 100,
213
231
  :samplerClass => FeldtRuby::Optimize::RadiusLimitedPopulationSampler,
214
- :samplerRadius => 8 # Max distance between individuals selected in same tournament.
232
+ :samplerRadius => 8, # Max distance between individuals selected in same tournament.
233
+ :archive => nil, # If this is set it takes precedence over archiveClass.
234
+ :archiveClass => FeldtRuby::Optimize::Archive,
235
+ :archiveDiversityObjective => nil, # If this is set it takes precedence over the class in archiveDiversityObjectiveClass
236
+ :archiveDiversityObjectiveClass => FeldtRuby::Optimize::EuclideanDistanceToBest,
215
237
  }
216
238
 
217
239
  def self.override_default_options_with(options)
@@ -1,3 +1,3 @@
1
1
  module FeldtRuby
2
- VERSION = "0.4.9"
2
+ VERSION = "0.4.10"
3
3
  end
@@ -0,0 +1,218 @@
1
+ require 'feldtruby/optimize/archive'
2
+
3
+ class TwoMinOneMax < FeldtRuby::Optimize::Objective
4
+ def objective_min_1(x)
5
+ x.sum
6
+ end
7
+
8
+ def objective_min_2(x)
9
+ x.max
10
+ end
11
+
12
+ def objective_max_1(x)
13
+ x.min
14
+ end
15
+ end
16
+
17
+ class DivObj2 < FeldtRuby::Optimize::EuclideanDistanceToBest
18
+ # Diversity goal 2: AMGA2 crowding distance to nearest neighbours in fitness
19
+ # space. Phenotype diversity.
20
+ def goal_max_crowding_distance_to_nearest_neighbours(candidate)
21
+ # FIX: Below we need to handle the case where the candidate is an extremal
22
+ # solution and thus only has one nearest neighbour. Give it very high diversity?
23
+ nn1, nn2 = archive.find_nearest_neighbours_in_fitness(candidate)
24
+ sqv1 = archive.objective.quality_of(nn1).sub_qualitites
25
+ sqv2 = archive.objective.quality_of(nn2).sub_qualitites
26
+ sqvme = archive.objective.quality_of(candidate).sub_qualitites
27
+ ((sqv1 - sqvme) * (sqv2 - sqvme)).abs.sum
28
+ end
29
+ end
30
+
31
+ describe "Archive" do
32
+ before do
33
+ @o = TwoMinOneMax.new(FeldtRuby::Optimize::Objective::WeightedSumAggregator.new)
34
+ @do = FeldtRuby::Optimize::EuclideanDistanceToBest.new(FeldtRuby::Optimize::Objective::WeightedSumAggregator.new)
35
+ @a = FeldtRuby::Optimize::Archive.new(@o, @do, {
36
+ :NumTopPerGoal => 2,
37
+ :NumTopAggregate => 3,
38
+ :NumTopDiversityAggregate => 2})
39
+ end
40
+
41
+ it 'is adapted to objectives when created' do
42
+ @a.objective.must_equal @o
43
+ @a.diversity_objective.must_equal @do
44
+ @a.specialists.length.must_equal @o.num_goals
45
+ @a.generalists.must_be_kind_of FeldtRuby::Optimize::Archive::GlobalTopList
46
+ @a.weirdos.must_be_kind_of FeldtRuby::Optimize::Archive::GlobalTopList
47
+ end
48
+
49
+ it 'does not yet have a best value when created' do
50
+ @a.best.must_equal nil
51
+ end
52
+
53
+ it 'properly handles additions' do
54
+ i1 = [1,2,3]
55
+ @a.add i1 # [6, 3, 1] => 10
56
+
57
+ @a.best.must_equal i1
58
+
59
+ @a.generalists.length.must_equal 1
60
+ @a.generalists[0].must_equal i1
61
+
62
+ @a.specialists[0].length.must_equal 1
63
+ @a.specialists[1].length.must_equal 1
64
+ @a.specialists[2].length.must_equal 1
65
+
66
+ @a.weirdos.length.must_equal 0
67
+
68
+ i2 = [1,2,4]
69
+ @a.add i2 # [7, 4, 1] => 12
70
+
71
+ @a.best.must_equal i1
72
+
73
+ @a.generalists.length.must_equal 2
74
+ @a.generalists[0].must_equal i1
75
+ @a.generalists[1].must_equal i2
76
+
77
+ @a.specialists[0].length.must_equal 2
78
+ @a.specialists[1].length.must_equal 2
79
+ @a.specialists[2].length.must_equal 2
80
+ @a.specialists[0][0].must_equal i1
81
+ @a.specialists[0][1].must_equal i2
82
+ @a.specialists[1][0].must_equal i1
83
+ @a.specialists[1][1].must_equal i2
84
+
85
+ @a.weirdos.length.must_equal 0
86
+
87
+ i3 = [1,2,0] # [3, 2, 0] => 5
88
+ @a.add i3
89
+
90
+ @a.best.must_equal i3
91
+ @a.generalists.length.must_equal 3
92
+ @a.generalists[0].must_equal i3
93
+ @a.generalists[1].must_equal i1
94
+ @a.generalists[2].must_equal i2
95
+
96
+ @a.specialists[0].length.must_equal 2
97
+ @a.specialists[1].length.must_equal 2
98
+ @a.specialists[2].length.must_equal 2
99
+ @a.specialists[0][0].must_equal i3
100
+ @a.specialists[0][1].must_equal i1
101
+ @a.specialists[1][0].must_equal i3
102
+ @a.specialists[1][1].must_equal i1
103
+
104
+ @a.weirdos.length.must_equal 0
105
+
106
+ # Introduce a new baddest one
107
+ i4 = [5,5,10] # [20, 10, 5] => 35
108
+ @a.add i4
109
+
110
+ @a.best.must_equal i3
111
+ @a.generalists.length.must_equal 3
112
+ @a.generalists[0].must_equal i3
113
+ @a.generalists[1].must_equal i1
114
+ @a.generalists[2].must_equal i2
115
+
116
+ @a.specialists[0].length.must_equal 2
117
+ @a.specialists[1].length.must_equal 2
118
+ @a.specialists[2].length.must_equal 2
119
+ @a.specialists[0][0].must_equal i3
120
+ @a.specialists[0][1].must_equal i1
121
+ @a.specialists[1][0].must_equal i3
122
+ @a.specialists[1][1].must_equal i1
123
+
124
+ @a.weirdos.length.must_equal 0
125
+
126
+ # Introduce a new 2nd best
127
+ i5 = [3,0,1] # [4, 3, 0] => 7
128
+ @a.add i5
129
+
130
+ @a.best.must_equal i3
131
+ @a.generalists.length.must_equal 3
132
+ @a.generalists[0].must_equal i3
133
+ @a.generalists[1].must_equal i5
134
+ @a.generalists[2].must_equal i1
135
+
136
+ @a.specialists[0].length.must_equal 2
137
+ @a.specialists[1].length.must_equal 2
138
+ @a.specialists[2].length.must_equal 2
139
+ @a.specialists[0][0].must_equal i3
140
+ @a.specialists[0][1].must_equal i5
141
+ @a.specialists[1][0].must_equal i3
142
+ @a.specialists[1][1].must_equal i1
143
+
144
+ @a.weirdos.length.must_equal 0
145
+
146
+ # Introduce 2 new that is close to best and goes into top list but also into
147
+ # weirdos.
148
+ i6 = [1.05,2.05,0] # [3.1, 2.05, 0] => 5.15
149
+ @a.add i6
150
+
151
+ i7 = [1.01,2.00,0.10] # [3.11, 2.00, 0.1] => 5.01
152
+ @a.add i7
153
+
154
+ @a.best.must_equal i3
155
+ @a.generalists.length.must_equal 3
156
+ @a.generalists[0].must_equal i3
157
+ @a.generalists[1].must_equal i7
158
+ @a.generalists[2].must_equal i6
159
+
160
+ @a.specialists[0].length.must_equal 2
161
+ @a.specialists[1].length.must_equal 2
162
+ @a.specialists[2].length.must_equal 2
163
+ @a.specialists[0][0].must_equal i3
164
+ @a.specialists[0][1].must_equal i6
165
+ @a.specialists[1][0].must_equal i3
166
+ @a.specialists[1][1].must_equal i7
167
+
168
+ @a.weirdos.length.must_equal 2
169
+ @a.weirdos[0].must_equal i7
170
+ @a.weirdos[1].must_equal i6
171
+
172
+ # Introduce one weird which is not good enough to go into the generalists
173
+ # or specialists but goes on top of weirdos.
174
+ i8 = [1.03, 2.10, 0.1] # [3.23, 2.10, 0.1] => 5.23
175
+ @a.good_enough_quality_to_be_interesting?(i8).must_equal true
176
+ @a.add i8
177
+
178
+ @a.best.must_equal i3
179
+ @a.generalists.length.must_equal 3
180
+ @a.generalists[0].must_equal i3
181
+ @a.generalists[1].must_equal i7
182
+ @a.generalists[2].must_equal i6
183
+
184
+ @a.specialists[0].length.must_equal 2
185
+ @a.specialists[1].length.must_equal 2
186
+ @a.specialists[2].length.must_equal 2
187
+ @a.specialists[0][0].must_equal i3
188
+ @a.specialists[0][1].must_equal i6
189
+ @a.specialists[1][0].must_equal i3
190
+ @a.specialists[1][1].must_equal i7
191
+
192
+ @a.weirdos.length.must_equal 2
193
+ @a.weirdos[0].must_equal i8
194
+ @a.weirdos[1].must_equal i7
195
+
196
+ # Less weird than i8 but still goes into weirdos.
197
+ i9 = [1.04, 2.08, 0.05] # [3.17, 2.08, 0.05] => 5.20
198
+ @a.add i9
199
+
200
+ @a.best.must_equal i3
201
+ @a.generalists.length.must_equal 3
202
+ @a.generalists[0].must_equal i3
203
+ @a.generalists[1].must_equal i7
204
+ @a.generalists[2].must_equal i6
205
+
206
+ @a.specialists[0].length.must_equal 2
207
+ @a.specialists[1].length.must_equal 2
208
+ @a.specialists[2].length.must_equal 2
209
+ @a.specialists[0][0].must_equal i3
210
+ @a.specialists[0][1].must_equal i6
211
+ @a.specialists[1][0].must_equal i3
212
+ @a.specialists[1][1].must_equal i7
213
+
214
+ @a.weirdos.length.must_equal 2
215
+ @a.weirdos[0].must_equal i8
216
+ @a.weirdos[1].must_equal i9
217
+ end
218
+ end
metadata CHANGED
@@ -1,83 +1,94 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: feldtruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.9
4
+ version: 0.4.10
5
+ prerelease:
5
6
  platform: ruby
6
7
  authors:
7
8
  - Robert Feldt
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2013-04-11 00:00:00.000000000 Z
12
+ date: 2013-04-18 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: rinruby
15
16
  requirement: !ruby/object:Gem::Requirement
17
+ none: false
16
18
  requirements:
17
- - - '>='
19
+ - - ! '>='
18
20
  - !ruby/object:Gem::Version
19
21
  version: '0'
20
22
  type: :runtime
21
23
  prerelease: false
22
24
  version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
23
26
  requirements:
24
- - - '>='
27
+ - - ! '>='
25
28
  - !ruby/object:Gem::Version
26
29
  version: '0'
27
30
  - !ruby/object:Gem::Dependency
28
31
  name: json
29
32
  requirement: !ruby/object:Gem::Requirement
33
+ none: false
30
34
  requirements:
31
- - - '>='
35
+ - - ! '>='
32
36
  - !ruby/object:Gem::Version
33
37
  version: '0'
34
38
  type: :runtime
35
39
  prerelease: false
36
40
  version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
37
42
  requirements:
38
- - - '>='
43
+ - - ! '>='
39
44
  - !ruby/object:Gem::Version
40
45
  version: '0'
41
46
  - !ruby/object:Gem::Dependency
42
47
  name: nokogiri
43
48
  requirement: !ruby/object:Gem::Requirement
49
+ none: false
44
50
  requirements:
45
- - - '>='
51
+ - - ! '>='
46
52
  - !ruby/object:Gem::Version
47
53
  version: '0'
48
54
  type: :runtime
49
55
  prerelease: false
50
56
  version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
51
58
  requirements:
52
- - - '>='
59
+ - - ! '>='
53
60
  - !ruby/object:Gem::Version
54
61
  version: '0'
55
62
  - !ruby/object:Gem::Dependency
56
63
  name: mongo
57
64
  requirement: !ruby/object:Gem::Requirement
65
+ none: false
58
66
  requirements:
59
- - - '>='
67
+ - - ! '>='
60
68
  - !ruby/object:Gem::Version
61
69
  version: '0'
62
70
  type: :runtime
63
71
  prerelease: false
64
72
  version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
65
74
  requirements:
66
- - - '>='
75
+ - - ! '>='
67
76
  - !ruby/object:Gem::Version
68
77
  version: '0'
69
78
  - !ruby/object:Gem::Dependency
70
79
  name: bson_ext
71
80
  requirement: !ruby/object:Gem::Requirement
81
+ none: false
72
82
  requirements:
73
- - - '>='
83
+ - - ! '>='
74
84
  - !ruby/object:Gem::Version
75
85
  version: '0'
76
86
  type: :runtime
77
87
  prerelease: false
78
88
  version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
79
90
  requirements:
80
- - - '>='
91
+ - - ! '>='
81
92
  - !ruby/object:Gem::Version
82
93
  version: '0'
83
94
  description: Robert Feldt's Common Ruby Code lib
@@ -112,8 +123,8 @@ files:
112
123
  - lib/feldtruby/mongodb_logger.rb
113
124
  - lib/feldtruby/net/html_doc_getter.rb
114
125
  - lib/feldtruby/optimize.rb
126
+ - lib/feldtruby/optimize/archive.rb
115
127
  - lib/feldtruby/optimize/differential_evolution.rb
116
- - lib/feldtruby/optimize/elite_archive.rb
117
128
  - lib/feldtruby/optimize/max_steps_termination_criterion.rb
118
129
  - lib/feldtruby/optimize/objective.rb
119
130
  - lib/feldtruby/optimize/optimizer.rb
@@ -167,8 +178,8 @@ files:
167
178
  - test/test_normalization.rb
168
179
  - test/test_object_annotations.rb
169
180
  - test/test_optimize.rb
181
+ - test/test_optimize_archive.rb
170
182
  - test/test_optimize_differential_evolution.rb
171
- - test/test_optimize_elite_archive.rb
172
183
  - test/test_optimize_objective.rb
173
184
  - test/test_optimize_populationbasedoptimizer.rb
174
185
  - test/test_optimize_random_search.rb
@@ -185,26 +196,27 @@ files:
185
196
  - test/tmp_shorter.csv
186
197
  homepage: https://github.com/robertfeldt/feldtruby
187
198
  licenses: []
188
- metadata: {}
189
199
  post_install_message:
190
200
  rdoc_options: []
191
201
  require_paths:
192
202
  - lib
193
203
  required_ruby_version: !ruby/object:Gem::Requirement
204
+ none: false
194
205
  requirements:
195
- - - '>='
206
+ - - ! '>='
196
207
  - !ruby/object:Gem::Version
197
208
  version: '0'
198
209
  required_rubygems_version: !ruby/object:Gem::Requirement
210
+ none: false
199
211
  requirements:
200
- - - '>='
212
+ - - ! '>='
201
213
  - !ruby/object:Gem::Version
202
214
  version: '0'
203
215
  requirements: []
204
216
  rubyforge_project:
205
- rubygems_version: 2.0.0
217
+ rubygems_version: 1.8.25
206
218
  signing_key:
207
- specification_version: 4
219
+ specification_version: 3
208
220
  summary: Robert Feldt's Common Ruby Code lib
209
221
  test_files:
210
222
  - test/helper.rb
@@ -226,8 +238,8 @@ test_files:
226
238
  - test/test_normalization.rb
227
239
  - test/test_object_annotations.rb
228
240
  - test/test_optimize.rb
241
+ - test/test_optimize_archive.rb
229
242
  - test/test_optimize_differential_evolution.rb
230
- - test/test_optimize_elite_archive.rb
231
243
  - test/test_optimize_objective.rb
232
244
  - test/test_optimize_populationbasedoptimizer.rb
233
245
  - test/test_optimize_random_search.rb
checksums.yaml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- SHA1:
3
- metadata.gz: 649d3dfa9702ca3b927e1f439220f1aeed6ca970
4
- data.tar.gz: 3bc58c5d5c11417907254bc22d4b974b1725bed6
5
- SHA512:
6
- metadata.gz: e02ff98f8487db6542dc5d9d86c53d7e1a8e228b872d2a1e51f075fcc6b8f4ad2025d5123d1e8f2e84a3ee7efc72b4fce411464696e8181e07a8227fbbd06f0e
7
- data.tar.gz: 79e5b1e5ee1cd35181e3675c159d242a9cd1861218f361a6b545f4753b38af329427dd28d1b2d83c690aebe96d915e97c3894d24c71d1ddca8da689c0f39040e
@@ -1,91 +0,0 @@
1
- require 'feldtruby/optimize'
2
-
3
- module FeldtRuby::Optimize
4
-
5
- # This keeps a record of the best/elite candidate solutions found during
6
- # an optimization search. It keeps separate top lists per goal being optimized
7
- # as well as for the aggregate quality value (fitness) itself. The top lists
8
- # are all sorted to allow for fast checks and insertion.
9
- class EliteArchive
10
- DefaultParams = {
11
- :NumTopPerGoal => 10,
12
- :NumTopAggregate => 25
13
- }
14
-
15
- attr_reader :objective, :top_per_goal, :best
16
-
17
- def initialize(objective, options = DefaultParams.clone)
18
- @objective = objective
19
- @options = options
20
- init_top_lists
21
- end
22
-
23
- # A top list is an array of a fixed size that saves the top candidates
24
- # based on their quality values.
25
- class GlobalTopList
26
- def initialize(maxSize, objective)
27
- @max_size =maxSize
28
- @top_list = Array.new
29
- @objective = objective
30
- end
31
- def length; @top_list.length; end
32
-
33
- def [](index)
34
- @top_list[index]
35
- end
36
-
37
- def add(candidate)
38
- last = @top_list.last
39
- #puts "In #{self},\nlast = #{last}, candidate = #{candidate}, top_list = #{@top_list}"
40
- if @top_list.length < @max_size || last.nil? || is_better_than?(candidate, last)
41
- @top_list.pop if @top_list.length >= @max_size
42
- @top_list << candidate
43
- @top_list = sort_top_list
44
- end
45
- #puts "top_list = #{@top_list}"
46
- end
47
-
48
- def is_better_than?(candidate1, candidate2)
49
- @objective.is_better_than?(candidate1, candidate2)
50
- end
51
-
52
- def sort_top_list
53
- @top_list.sort_by {|c| @objective.quality_of(c).value}
54
- end
55
-
56
- def inspect
57
- self.class.inspect + @top_list.inspect
58
- end
59
- end
60
-
61
- class GoalTopList < GlobalTopList
62
- def initialize(maxSize, objective, goalIndex)
63
- super(maxSize, objective)
64
- @index = goalIndex
65
- end
66
- def is_better_than?(candidate1, candidate2)
67
- @objective.is_better_than_for_goal?(@index, candidate1, candidate2)
68
- end
69
- def sort_top_list
70
- @top_list.sort_by {|c|
71
- qv = @objective.quality_of(c)
72
- qv.sub_quality(@index, true) # We want the sub quality value posed as a minimization goal regardless of whether it is a min or max goal
73
- }
74
- end
75
- end
76
-
77
- def init_top_lists
78
- @top_per_goal = Array.new
79
- @objective.num_goals.times do |i|
80
- @top_per_goal << GoalTopList.new(@options[:NumTopPerGoal], @objective, i)
81
- end
82
- @best = GlobalTopList.new(@options[:NumTopAggregate], @objective)
83
- end
84
-
85
- def add(candidate)
86
- @best.add candidate
87
- @top_per_goal.each {|tl| tl.add(candidate)}
88
- end
89
- end
90
-
91
- end
@@ -1,85 +0,0 @@
1
- require 'feldtruby/optimize/elite_archive'
2
-
3
- class TwoMinOneMax < FeldtRuby::Optimize::Objective
4
- def objective_min_1(x)
5
- x.sum
6
- end
7
-
8
- def objective_min_2(x)
9
- x.max
10
- end
11
-
12
- def objective_max_1(x)
13
- x.min
14
- end
15
- end
16
-
17
- describe "EliteArchive" do
18
- before do
19
- @o = TwoMinOneMax.new(FeldtRuby::Optimize::Objective::WeightedSumAggregator.new)
20
- @a = FeldtRuby::Optimize::EliteArchive.new(@o, {
21
- :NumTopPerGoal => 2,
22
- :NumTopAggregate => 3})
23
- end
24
-
25
- it 'is adapted to an objective when created' do
26
- @a.objective.must_equal @o
27
- @a.top_per_goal.length.must_equal @o.num_goals
28
- end
29
-
30
- it 'properly handles additions' do
31
- i1 = [1,2,3]
32
- @a.add i1 # [6, 3, 1] => 8
33
- @a.best.length.must_equal 1
34
- @a.top_per_goal[0].length.must_equal 1
35
- @a.top_per_goal[1].length.must_equal 1
36
- @a.top_per_goal[2].length.must_equal 1
37
-
38
- i2 = [1,2,4]
39
- @a.add i2 # [7, 4, 1] => 10
40
- @a.best.length.must_equal 2
41
- @a.top_per_goal[0].length.must_equal 2
42
- @a.top_per_goal[1].length.must_equal 2
43
- @a.top_per_goal[2].length.must_equal 2
44
- @a.top_per_goal[0][0].must_equal i1
45
- @a.top_per_goal[0][1].must_equal i2
46
- @a.top_per_goal[1][0].must_equal i1
47
- @a.top_per_goal[1][1].must_equal i2
48
-
49
- i3 = [1,2,0] # [3, 2, 0] => 5
50
- @a.add i3
51
-
52
- @a.best.length.must_equal 3
53
- @a.top_per_goal[0].length.must_equal 2
54
- @a.top_per_goal[1].length.must_equal 2
55
- @a.top_per_goal[2].length.must_equal 2
56
-
57
- @a.best[0].must_equal i3
58
- @a.best[1].must_equal i1
59
- @a.best[2].must_equal i2
60
-
61
- @a.top_per_goal[0][0].must_equal i3
62
- @a.top_per_goal[0][1].must_equal i1
63
-
64
- @a.top_per_goal[1][0].must_equal i3
65
- @a.top_per_goal[1][1].must_equal i1
66
-
67
- @a.top_per_goal[2][0].must_equal i1
68
- @a.top_per_goal[2][1].must_equal i2
69
-
70
- i4 = [5,5,10] # [20, 10, 5] => 25
71
- @a.add i4
72
-
73
- @a.best.length.must_equal 3
74
- @a.top_per_goal[0].length.must_equal 2
75
- @a.top_per_goal[1].length.must_equal 2
76
- @a.top_per_goal[2].length.must_equal 2
77
-
78
- @a.best[0].must_equal i3
79
- @a.best[1].must_equal i1
80
- @a.best[2].must_equal i2
81
-
82
- @a.top_per_goal[2][0].must_equal i4
83
- @a.top_per_goal[2][1].must_equal i1
84
- end
85
- end