Class: FeldtRuby::Optimize::Objective

Inherits:
Object
  • Object
show all
Includes:
Logging
Defined in:
lib/feldtruby/optimize/objective.rb

Overview

An Objective maps candidate solutions to qualities so they can be compared and ranked.

One objective can have one or more sub-objectives, called goals. Each goal is specified as a separate method and its name indicates if the returned Numeric should be minimized or maximized. To create your own objective you subclass and add instance methods named as

goal_min_qualityAspectName (for a goal value to be minimized), or 
goal_max_qualityAspectName (for a goal value to be minimized).

An objective keeps track of the min and max value that has been seen so far for each goal. An objective has version numbers to indicate the number of times a new min or max value has been identified for a goal.

This base class uses weigthed sum as the quality mapper and number comparator as the comparator. But the mapper and comparator to be used can, of course, be changed.

Defined Under Namespace

Classes: LowerAggregateQualityIsBetterComparator, MeanWeigthedGlobalRatios, QualityAggregator, WeightedSumAggregator

Instance Attribute Summary collapse

Attributes included from Logging

#logger

Instance Method Summary collapse

Methods included from Logging

#__find_logger_set_on_instance_vars, #new_default_logger, #setup_logger_and_distribute_to_instance_variables

Constructor Details

#initialize(qualityAggregator = MeanWeigthedGlobalRatios.new, comparator = LowerAggregateQualityIsBetterComparator.new) ⇒ Objective

WeightedSumAggregator.new,



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/feldtruby/optimize/objective.rb', line 44

def initialize(qualityAggregator = MeanWeigthedGlobalRatios.new, #WeightedSumAggregator.new, 
  comparator = LowerAggregateQualityIsBetterComparator.new)

  # A quality aggregator maps the goal values of a candidate to a single number.
  @aggregator = qualityAggregator
  @aggregator.objective = self

  # A comparator compares two or more candidates and ranks them based on their
  # qualities.
  @comparator = comparator
  @comparator.objective = self

  self.current_version = 0

  # We set all mins to INFINITY. This ensures that the first value seen will
  # be smaller, and thus set as the new min.
  @global_min_values_per_goal = [Float::INFINITY] * num_goals

  # We set all maxs to -INFINITY. This ensures that the first value seen will
  # be larger, and thus set as the new max.
  @global_max_values_per_goal = [-Float::INFINITY] * num_goals

  setup_logger_and_distribute_to_instance_variables()

  # An array to keep the best object per goal.
  @best_objects = Array.new

end

Instance Attribute Details

#best_candidateObject (readonly)

Returns the value of attribute best_candidate.



219
220
221
# File 'lib/feldtruby/optimize/objective.rb', line 219

def best_candidate
  @best_candidate
end

#current_versionObject

Current version of this objective. Is updated when the min or max values for a sub-objective has been updated or when the weights are changed. Candidates are always compared based on the latest version of an objective.



40
41
42
# File 'lib/feldtruby/optimize/objective.rb', line 40

def current_version
  @current_version
end

#global_max_values_per_goalObject (readonly)

Returns the value of attribute global_max_values_per_goal.



42
43
44
# File 'lib/feldtruby/optimize/objective.rb', line 42

def global_max_values_per_goal
  @global_max_values_per_goal
end

#global_min_values_per_goalObject (readonly)

Returns the value of attribute global_min_values_per_goal.



42
43
44
# File 'lib/feldtruby/optimize/objective.rb', line 42

def global_min_values_per_goal
  @global_min_values_per_goal
end

Instance Method Details

#aggregated_quality(subQualities) ⇒ Object

Return the aggregated quality value given sub qualities.



204
205
206
# File 'lib/feldtruby/optimize/objective.rb', line 204

def aggregated_quality(subQualities)
  @aggregator.aggregate_from_sub_qualities(subQualities, weights)
end

#calculate_sub_qualities_of(candidate, updateGlobals = true) ⇒ Object

Calculate the sub-qualities from scratch, i.e. by mapping the candidate to an object to be evaluated and then calculate the value of each goal.



141
142
143
144
145
146
# File 'lib/feldtruby/optimize/objective.rb', line 141

def calculate_sub_qualities_of(candidate, updateGlobals = true)
  obj = map_candidate_to_object_to_be_evaluated(candidate)
  sub_qualitites = goal_methods.map {|gmethod| self.send(gmethod, obj)}
  update_global_mins_and_maxs(sub_qualitites, candidate) if updateGlobals
  sub_qualitites
end

#goal_methodsObject

Return the names of the goal methods.



79
80
81
# File 'lib/feldtruby/optimize/objective.rb', line 79

def goal_methods
  @goal_methods ||= self.methods.select {|m| is_goal_method?(m)}
end

#hat_compare(candidate1, candidate2) ⇒ Object

Return true iff candidate1 is better than candidate2. Will update their quality values if they are out of date.



193
194
195
# File 'lib/feldtruby/optimize/objective.rb', line 193

def hat_compare(candidate1, candidate2)
  @comparator.hat_compare(candidate1, candidate2)
end

#invalidate_quality_of(candidate) ⇒ Object

Invalidate the current quality object to ensure it will be recalculated the next time quality_of is called with this candidate. This is needed when there is a new best candidate set in an Archive, for example.



169
170
171
# File 'lib/feldtruby/optimize/objective.rb', line 169

def invalidate_quality_of(candidate)
  update_quality_value_in_object candidate, nil
end

#is_better_than?(candidate1, candidate2) ⇒ Boolean

Return true iff candidate1 is better than candidate2. Will update their quality values if they are out of date.

Returns:

  • (Boolean)


187
188
189
# File 'lib/feldtruby/optimize/objective.rb', line 187

def is_better_than?(candidate1, candidate2)
  @comparator.is_better_than?(candidate1, candidate2)
end

#is_better_than_for_goal?(index, candidate1, candidate2) ⇒ Boolean

Return true iff candidate1 is better than candidate2 for goal index. Will update their quality values if they are out of date.

Returns:

  • (Boolean)


199
200
201
# File 'lib/feldtruby/optimize/objective.rb', line 199

def is_better_than_for_goal?(index, candidate1, candidate2)
  @comparator.is_better_than_for_goal?(index, candidate1, candidate2)
end

#is_goal_method?(methodNameAsSymbolOrString) ⇒ Boolean

Return true iff the method with the given name is a goal method.

Returns:

  • (Boolean)


89
90
91
# File 'lib/feldtruby/optimize/objective.rb', line 89

def is_goal_method?(methodNameAsSymbolOrString)
  (methodNameAsSymbolOrString.to_s =~ /^(goal|objective)_(min|max)_([\w_]+)$/) != nil
end

#is_min_goal?(index) ⇒ Boolean

Return true iff the goal method with the given index is a minimizing goal.

Returns:

  • (Boolean)


84
85
86
# File 'lib/feldtruby/optimize/objective.rb', line 84

def is_min_goal?(index)
  (@is_min_goal ||= (goal_methods.map {|m| is_min_goal_method?(m)}))[index]
end

#is_min_goal_method?(methodNameAsSymbolOrString) ⇒ Boolean

Return true iff the method with the given name is a goal method.

Returns:

  • (Boolean)


94
95
96
# File 'lib/feldtruby/optimize/objective.rb', line 94

def is_min_goal_method?(methodNameAsSymbolOrString)
  (methodNameAsSymbolOrString.to_s =~ /^(goal|objective)_min_([\w_]+)$/) != nil
end

#map_candidate_to_object_to_be_evaluated(candidate) ⇒ Object

The candidate objects can be mapped to another object before we call the goal methods to calc the quality values. Default is no mapping but subclasses can override this for more complex evaluation schemes.



101
102
103
# File 'lib/feldtruby/optimize/objective.rb', line 101

def map_candidate_to_object_to_be_evaluated(candidate)
  candidate
end

#note_end_of_optimization(optimizer) ⇒ Object



208
209
210
211
212
213
214
215
216
217
# File 'lib/feldtruby/optimize/objective.rb', line 208

def note_end_of_optimization(optimizer)
  logger.log_data :objective_optimization_ends, {
    "Best object, aggregated" => @best_candidate,
    "Quality of best object" => @best_quality_value,
    "Version" => current_version,
    "Comparator" => @comparator,
    "Aggregator" => @aggregator,
    "Best objects per goal" => @best_objects
  }, "Objective: Report at end of optimization", true
end

#num_goalsObject

Return the number of goals of this objective.



74
75
76
# File 'lib/feldtruby/optimize/objective.rb', line 74

def num_goals
  @num_goals ||= goal_methods.length
end

#quality_of(candidate, weights = self.weights) ⇒ Object

Return a quality value for a given candidate and weights for the whole objective for a given candidate. Updates the best candidate if this is the best seen so far.



151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/feldtruby/optimize/objective.rb', line 151

def quality_of(candidate, weights = self.weights)

  q = quality_in_object(candidate)
  return q if q

  sub_qualities = sub_qualities_of(candidate)

  qv = update_quality_value_of candidate, sub_qualities, weights

  update_best_candidate candidate, qv

  qv

end

#rank_candidates(candidates, weights = self.weights) ⇒ Object

Rank candidates from best to worst. Candidates that have the same quality are randomly ordered.



175
176
177
178
179
180
181
182
183
# File 'lib/feldtruby/optimize/objective.rb', line 175

def rank_candidates(candidates, weights = self.weights)
  # We first ensure all candidates have calculated sub-qualities, then we
  # call the comparator. This ensures that the gobals are already correctly
  # updated on each quality value.
  candidates.map {|c| sub_qualities_of(c, true)}

  # Now just let the comparator rank the candidates
  @comparator.rank_candidates candidates, weights
end

#sub_qualities_of(candidate, updateGlobals = true) ⇒ Object

Return a vector of the “raw” quality values, i.e. the fitness value for each goal. If we have already calculated the sub-qualities we just return them. If not we calculate them.



132
133
134
135
136
137
# File 'lib/feldtruby/optimize/objective.rb', line 132

def sub_qualities_of(candidate, updateGlobals = true)
  # Return the sub_qualities if we already have calculated them.
  sqs = sub_qualities_if_already_calculated?(candidate)
  return sqs if sqs
  calculate_sub_qualities_of candidate, updateGlobals
end

#weightsObject

Weights is a map from goal method names to a number that represents the weight for that goal. Default is to set all weights to 1.



107
108
109
# File 'lib/feldtruby/optimize/objective.rb', line 107

def weights
  @weights ||= ([1] * num_goals)
end

#weights=(goalNameToNumberHash) ⇒ Object

Set the weights given a hash mapping each goal method name to a number. The mapper and/or comparator can use the weights in their calculations.



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/feldtruby/optimize/objective.rb', line 113

def weights=(goalNameToNumberHash)

  raise "Must be same number of weights as there are goals (#{num_aspects}), but is #{weights.length}" unless weights.length == num_goals

  weights = goal_methods.map {|gm| goalNameToNumberHash[gm]}

  logger.log_value :objective_weights_changed, 
    {"New weights" => goalNameToNumberHash}, 
    "Weights updated from #{@weights} to #{weights}"

  inc_version_number

  @weights = weights

end