Ultra 0.9.4
Loading...
Searching...
No Matches
evolution_replacement.tcc
Go to the documentation of this file.
1
12
13#if !defined(ULTRA_EVOLUTION_REPLACEMENT_H)
14# error "Don't include this file directly, include the specific .h instead"
15#endif
16
17#if !defined(ULTRA_EVOLUTION_REPLACEMENT_TCC)
18#define ULTRA_EVOLUTION_REPLACEMENT_TCC
19
24template<Evaluator E>
25strategy<E>::strategy(E &eva, const parameters &params)
26 : eva_(eva), params_(params)
27{
28}
29
39template<Evaluator E>
40template<Population P>
41bool tournament<E>::operator()(P &pop, const scored_t &offspring,
42 status_t &status) const
43{
44 static_assert(std::is_same_v<individual_t, typename P::value_type>);
45
46 Expects(!this->params_.needs_init());
47
48 const auto rounds(this->params_.evolution.tournament_size);
49 assert(0 < rounds);
51
52 auto worst_coord(random::coord(pop));
53 auto worst_fitness(this->eva_(pop[worst_coord]));
54
55 for (std::size_t i(1); i < rounds; ++i)
56 {
57 const auto new_coord(random::coord(pop));
58 const auto new_fitness(this->eva_(pop[new_coord]));
59
60 if (new_fitness < worst_fitness)
61 {
62 worst_fitness = new_fitness;
63 worst_coord = new_coord;
64 }
65 }
66
67 status.update_if_better(offspring);
68
69 const auto elitism(this->params_.evolution.elitism);
70 assert(in_0_1(elitism));
71
72 const bool replace(offspring.fit > worst_fitness || !random::boolean(elitism));
73 if (replace)
74 pop[worst_coord] = offspring.ind;
75
76 return replace;
77}
78
86template<Evaluator E>
87template<PopulationWithMutex P>
88void alps<E>::try_promote_individuals(const P &from, P &to) const
89{
90 for (const auto &prg : from)
91 try_add_to_layer(to, prg);
92}
93
123template<Evaluator E>
124template<PopulationWithMutex P, class S>
125bool alps<E>::try_add_to_layer(alps_layer_pair<P> pops,
126 const S &incoming_source) const
127 requires (std::same_as<S, individual_t> || std::same_as<S, scored_t>)
128{
129 static_assert(std::is_same_v<individual_t, typename P::value_type>);
130
131 const individual_t &incoming([&] -> const individual_t &
132 {
133 if constexpr (std::same_as<S, scored_t>)
134 return incoming_source.ind;
135 else
136 return incoming_source;
137 }());
138
139 Expects(incoming.is_valid());
140
141 auto &pop(pops.primary());
142
143 if (!pop.allowed())
144 return false;
145
146 // ---- Check capacity and append if there is room ----
147 if (std::lock_guard lock(pop.mutex()); pop.size() < pop.allowed())
148 {
149 pop.push_back(incoming);
150 return true;
151 }
152
153 // Layer is full, can we replace an existing individual?
154 individual_t displaced;
155 assert(displaced.empty());
156
157 // ---- Snapshot candidates then evaluate (slow, no lock) ----
158 struct snapshot_t
159 {
160 typename P::coord coord {};
161 individual_t ind {};
162 };
163
164 const std::size_t ts(this->params_.evolution.tournament_size);
165 Expects(ts > 0);
166 Expects(ts <= parameters::evolution_parameters::max_tournament_size);
167
168 std::vector<snapshot_t> candidates;
169 candidates.reserve(ts);
170
171 const auto max_age([&]
172 {
173 std::shared_lock lock(pop.mutex());
174
175 // Sample `ts` candidates (with replacement). Copy individuals out.
176 for (std::size_t i(0); i < ts; ++i)
177 {
178 const auto c(random::coord(pop));
179
180 candidates.push_back({c, pop[c]});
181 }
182
183 return pop.max_age();
184 }());
185
186 // Evaluate copied candidates outside any population lock.
187 std::size_t worst_idx(0);
188 auto worst_fit(this->eva_(candidates[0].ind));
189 auto worst_age(candidates[0].ind.age());
190
191 for (std::size_t i(1); i < ts; ++i)
192 {
193 const auto trial_fit(this->eva_(candidates[i].ind));
194 const auto trial_age(candidates[i].ind.age());
195
196 if (trial_age > std::max(worst_age, max_age)
197 || (std::max(worst_age, trial_age) <= max_age
198 && trial_fit < worst_fit))
199 {
200 worst_idx = i;
201 worst_fit = trial_fit;
202 worst_age = trial_age;
203 }
204 }
205
206 bool replace_worst(false);
207
208 if (const auto incoming_age(incoming.age());
209 incoming_age <= max_age && worst_age > max_age) // age rule
210 {
211 // Incoming fits the layer, worst doesn't.
212 replace_worst = true;
213 }
214 else if (incoming_age > max_age && worst_age <= max_age) // age rule
215 {
216 // Incoming is too old and worst is still valid.
217 replace_worst = false;
218 }
219 else
220 {
221 const auto incoming_fit([&]
222 {
223 if constexpr (std::same_as<S, scored_t>)
224 return incoming_source.fit;
225 else
226 return this->eva_(incoming);
227 }());
228
229 replace_worst = incoming_fit >= worst_fit;
230 }
231
232 // ---- Commit (exclusive) ----
233 if (replace_worst)
234 {
235 const auto coord(candidates[worst_idx].coord);
236
237 std::lock_guard lock(pop.mutex());
238
239 // Equality intentionally ignores age. Concurrent changes occur only in the
240 // final layer, where age does not affect layer eligibility.
241 if (const bool changed(pop[coord] != candidates[worst_idx].ind); changed)
242 {
243 // In ALPS, every non-final layer is owned by a single thread, so a
244 // snapshot/commit mismatch should never happen and indicates a logic
245 // error. The final layer is different: multiple lower layers may
246 // concurrently promote individuals into it, so the sampled resident may
247 // legitimately change before commit.
248 if (pops.has_secondary())
249 {
250 ultraERROR("ALPS snapshot/commit mismatch in non-final layer{}",
251 pop.uid());
252 }
253
254 return false;
255 }
256
257 displaced = pop[coord];
258 pop[coord] = incoming;
259 }
260
261 const bool replaced(!displaced.empty());
262
263 if (pops.has_secondary() && replaced)
264 try_add_to_layer(
265 pops.secondary(),
266 scored_t(std::move(displaced), std::move(worst_fit)));
267
268 return replaced;
269}
270
271template<Evaluator E>
272template<PopulationWithMutex P, class S>
273bool alps<E>::try_add_to_layer(P &layer, const S &incoming) const
274 requires (std::same_as<S, individual_t> || std::same_as<S, scored_t>)
275{
276 return try_add_to_layer(alps_layer_pair(layer), incoming);
277}
278
291template<Evaluator E>
292template<PopulationWithMutex P>
293void alps<E>::operator()(alps_layer_pair<P> pops, const scored_t &offspring,
294 status_t &status) const
295{
296 static_assert(std::is_same_v<individual_t, typename P::value_type>);
297 Expects(!this->params_.needs_init());
298
299 const bool inserted(try_add_to_layer(pops, offspring));
300 const bool new_best(status.update_if_better(offspring));
301
302 if (inserted || !new_best || !pops.has_secondary())
303 return;
304
305 // If a rejected offspring is a new global best, try to preserve it in the
306 // last layer. The number of retries depends on the elitism parameter
307 // (`elitism == 0` disables this rescue attempt).
308 const auto elitism(this->params_.evolution.elitism);
309 assert(in_0_1(elitism));
310
311 auto retries(static_cast<unsigned>(std::ceil(elitism * 3.0)));
312
313 while (retries--)
314 if (try_add_to_layer(pops.secondary(), offspring))
315 break;
316}
317
318template<Evaluator E>
319bool de<E>::operator()(individual_t &target, const individual_t &offspring,
320 status_t &status) const
321{
322 Expects(!this->params_.needs_init());
323
324 const auto off_fit(this->eva_(offspring));
325 status.update_if_better(scored_individual(offspring, off_fit));
326
327 // The equality in `>=` helps the DE population to navigate the flat portion
328 // of a fitness landscape and to reduce the possibility of population
329 // becoming stagnant.
330 const auto elitism(this->params_.evolution.elitism);
331 assert(in_0_1(elitism));
332
333 if (const auto target_fit(this->eva_(target));
334 off_fit >= target_fit || !random::boolean(elitism))
335 {
336 target = offspring;
337 return true;
338 }
339
340 return false;
341}
342
343#endif // include guard
Definition alps.h:48
bool update_if_better(const scored_individual< I, F > &)
Definition evolution_status.h:35
void try_promote_individuals(const P &, P &) const
Definition evolution_replacement.h:89
bool operator()(P &, const scored_t &, status_t &) const
Definition evolution_replacement.h:42
bool boolean(double p)
Definition random.h:154
static constexpr std::size_t max_tournament_size
Definition parameters.h:180