feldtruby 0.4.9 → 0.4.10

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