Ultra 0.9.4
Loading...
Searching...
No Matches
evolution_strategy.h
Go to the documentation of this file.
1
12
13#if !defined(ULTRA_EVOLUTION_STRATEGY_H)
14#define ULTRA_EVOLUTION_STRATEGY_H
15
16#include "kernel/alps.h"
17#include "kernel/analyzer.h"
22#include "kernel/population.h"
23
24#include <thread>
25
26namespace ultra
27{
28
36/// This design uses static polymorphism: concrete strategies derive from
37/// `evolution_strategy<E>` and provide the required operations at compile
38/// time. No runtime polymorphism is involved.
39///
40/// The `evolution` driver owns the main generational loop, termination
41/// conditions, concurrency, and logging, while concrete strategies define
42/// how one evolutionary step is assembled and how generation-level
43/// maintenance is performed.
44///
45/// Strategies are expected to be:
46/// - stateless or minimally stateful;
47/// - reusable across runs;
48/// - independent from the evaluation logic.
49///
50/// The base class stores shared references to the optimisation problem and
51/// evaluator, and provides default no-op hooks where appropriate.
52///
53/// \remark
54/// Implementations should avoid performing expensive operations in
55/// constructors; initialisation should be deferred to `init()`.
56///
57/// \remark
58/// This is a low-level strategy interface. It does not tune or complete
59/// `problem::params` before using them. Callers that invoke strategies directly
60/// must ensure the parameters are already valid for that strategy. The usual public
61/// entry point is `search`, which fills generic defaults, applies strategy defaults,
62/// and preserves user-specified values before running evolution.
63///
64template<Evaluator E>
66{
67public:
68 using evaluator_type = E;
69 using fitness_t = evaluator_fitness_t<E>;
70 using individual_t = evaluator_individual_t<E>;
71
75 [[nodiscard]] static parameters shape(const parameters &p) { return p; }
76
85 template<Population P> void init(P &) const {}
86
87 template<Population P>
88 [[nodiscard]] P *refinement_subgroup(P &) const noexcept;
89
100 template<Population P> void after_generation(
102
103 [[nodiscard]] const problem &get_problem() const noexcept { return prob_; }
104
105 template<Population P>
106 [[nodiscard]] std::size_t max_parallelism(const P &) const noexcept;
107
108protected:
109 evolution_strategy(const problem &, E &);
110
111 template<LayeredPopulation P>
112 [[nodiscard]] static bool valid_layer(P &, typename P::layer_iter);
113
114 E &eva_;
115 const problem &prob_;
116};
117
119/// Basic ALPS strategy.
120///
121/// With ALPS, several instances of a search algorithm are run in parallel,
122/// each in its own age-layer, and the age of solutions is kept track of. The
123/// key properties of ALPS are:
124/// * each age-layer has its own sub-population of one or more candidate
125/// solutions (individuals);
126/// * each age-layer has a maximum age and it may not contain individuals
127/// older than that maximum age;
128/// * the age of individuals is based on when the original genetic material
129/// was created from random;
130/// * the search algorithm in a given age-layer can look at individuals in
131/// its own sub-population and at the sub-populations in younger age layers
132/// but it can only replace individuals in its own population;
133/// * at regular intervals, the search algorithm in the first age-layer is
134/// restarted.
135///
136/// Age is a measure of how long an individual's family of
137/// genotypic material has been in the population. Randomly generated
138/// individuals, such as those that are created when the search algorithm are
139/// started, start with an age of `0`. Each generation that an individual stays
140/// in the population its age is increased by one. Individuals that are
141/// created through mutation or recombination take the age of their oldest
142/// parent and add one to it. This differs from conventional measures of age,
143/// in which individuals created through applying some type of variation to
144/// an existing individual (e.g. mutation or recombination) start with an age
145/// of `0`.
146///
147/// The search algorithm in a given layer acts somewhat independently of the
148/// others, with an exception being that it can use individuals from both its
149/// layer and the layer below to generated new candidate solutions. Also,
150/// each age layer has an upper limit on the age of solutions it can contain.
151/// When an individual is too old for its current layer, it cannot be used to
152/// generate new individuals for that layer and eventually is removed from
153/// that layer. Optionally, an attempt can be made to move this individual up
154/// to the next layer -- in which case it replaces some individual there that
155/// it's better than. Finally, at regular intervals the bottom layer is
156/// replaced with a new sub-population of randomly generated individuals,
157/// each with an age of `0`.
158///
159/// \see <https://github.com/ghornby/alps>
160///
161template<Evaluator E>
163{
164public:
165 using typename alps_es::evolution_strategy::fitness_t;
166 using typename alps_es::evolution_strategy::individual_t;
167
168 alps_es(const problem &, E &);
169
170 template<LayeredPopulation P> [[nodiscard]] auto operations(
171 P &, typename P::layer_iter,
173
174 template<LayeredPopulation P> void init(P &) const;
175
176 template<LayeredPopulation P>
177 [[nodiscard]] auto *refinement_subgroup(P &) const noexcept;
178
179 template<LayeredPopulation P> void after_generation(
181
182 template<LayeredPopulation P>
183 [[nodiscard]] std::size_t max_parallelism(const P &) const noexcept;
184
185 [[nodiscard]] static parameters shape(parameters);
186
187private:
188 const selection::alps<E> select_;
189 const recombination::base<E> recombine_;
190 replacement::alps<E> replace_;
191}; // class alps_es
192
196template<Evaluator E>
197class std_es : public evolution_strategy<E>
198{
199public:
200 using typename std_es::evolution_strategy::fitness_t;
201 using typename std_es::evolution_strategy::individual_t;
202
203 std_es(const problem &, E &);
204
205 template<SizedRandomAccessPopulation P> [[nodiscard]] auto operations(
207 template<LayeredPopulation P> [[nodiscard]] auto operations(
208 P &, typename P::layer_iter,
210
211private:
212 const selection::tournament<E> select_;
213 const recombination::base<E> recombine_;
214 const replacement::tournament<E> replace_;
215}; // class std_es
216
223template<Evaluator E>
224class de_es : public evolution_strategy<E>
225{
226public:
227 using typename de_es::evolution_strategy::fitness_t;
228 using typename de_es::evolution_strategy::individual_t;
229
230 de_es(const problem &, E &);
231
232 template<Population P> P *refinement_subgroup(P &) const noexcept = delete;
233
234 template<SizedRandomAccessPopulation P> [[nodiscard]] auto operations(
236
237 template<LayeredPopulation P> [[nodiscard]] auto operations(
238 P &, typename P::layer_iter,
240
241private:
242 const selection::de select_ {};
243 const recombination::de recombine_;
244 const replacement::de<E> replace_;
245}; // class de_es
246
247
251/// A type satisfies `Strategy` if, after removing cv/ref qualifiers:
252/// - it exposes an `evaluator_type`;
253/// - that type satisfies `Evaluator`;
254/// - the type derives from `evolution_strategy<evaluator_type>`.
255///
256/// This allows the concept to recognise both `evolution_strategy<E>` itself
257/// and concrete strategy classes derived from it, such as `alps_es<E>`,
258/// `std_es<E>`, and `de_es<E>`.
259///
260template<class T> concept Strategy =
261 requires
262 {
263 typename std::remove_cvref_t<T>::evaluator_type;
264 }
266 && std::derived_from<
267 std::remove_cvref_t<T>,
269
270template<class ES, class P> concept RefinementStrategy =
273 && requires(const std::remove_reference_t<ES> &strategy, P &pop)
275 requires Population<
276 std::remove_pointer_t<decltype(strategy.refinement_subgroup(pop))>>;
277
278 { strategy.refinement_subgroup(pop) } -> std::convertible_to<
279 std::remove_pointer_t<decltype(strategy.refinement_subgroup(pop))> *>;
280 };
281
283} // namespace ultra
284
285#endif // include guard
alps_es(const problem &, E &)
Definition evolution_strategy.h:152
void after_generation(P &, const summary< individual_t, fitness_t > &)
Definition evolution_strategy.h:274
void init(P &) const
Definition evolution_strategy.h:215
auto * refinement_subgroup(P &) const noexcept
Definition evolution_strategy.h:234
auto operations(P &, typename P::layer_iter, const evolution_status< individual_t, fitness_t > &) const
Definition evolution_strategy.h:183
std::size_t max_parallelism(const P &) const noexcept
Definition evolution_strategy.h:251
static parameters shape(parameters)
Definition evolution_strategy.h:366
auto operations(P &, const evolution_status< individual_t, fitness_t > &) const
Definition evolution_strategy.h:465
Definition evolution_status.h:36
Definition evolution_strategy.h:66
void init(P &) const
Definition evolution_strategy.h:85
std::size_t max_parallelism(const P &) const noexcept
Definition evolution_strategy.h:85
P * refinement_subgroup(P &) const noexcept
Definition evolution_strategy.h:67
static parameters shape(const parameters &p)
Definition evolution_strategy.h:75
void after_generation(P &, const summary< individual_t, fitness_t > &)
Definition evolution_strategy.h:119
evolution_strategy(const problem &, E &)
Definition evolution_strategy.h:36
Definition evolution_recombination.h:71
Definition evolution_recombination.h:89
Definition evolution_replacement.h:80
Definition evolution_replacement.h:107
Definition evolution_replacement.h:54
Definition evolution_selection.h:83
Definition evolution_selection.h:100
Definition evolution_selection.h:66
auto operations(P &, const evolution_status< individual_t, fitness_t > &) const
Definition evolution_strategy.h:401
Definition evolution_summary.h:33
Definition evaluator.h:44
Definition population.h:37
Definition evolution_strategy.h:270
Definition evolution_strategy.h:260
Definition parameters.h:26
Definition problem.h:26