differential_evolution/lib.rs
1// Copyright 2016 Martin Ankerl.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9//! Differential Evolution optimizer for rust.
10//!
11//! Simple and powerful global optimization using a
12//! [Self-Adapting Differential Evolution](http://bit.ly/2cMPiMj)
13//! for Rust. See Wikipedia's article on
14//! [Differential Evolution](https://en.wikipedia.org/wiki/Differential_evolution)
15//! for more information.
16//!
17//! ## Usage
18//!
19//! Add this to your `Cargo.toml`:
20//!
21//! ```toml
22//! [dependencies]
23//! differential-evolution = "*"
24//! ```
25//!
26//! and this to your crate root:
27//!
28//! ```rust
29//! extern crate differential_evolution;
30//! ```
31//!
32//! ## Examples
33//!
34//! Differential Evolution is a global optimization algorithm that
35//! tries to iteratively improve candidate solutions with regards to
36//! a user-defined cost function.
37//!
38//! ### Quick Start: Sum of Squares
39//! This example finds the minimum of a simple 5-dimensional function.
40//!
41//! ```
42//! extern crate differential_evolution;
43//!
44//! use differential_evolution::self_adaptive_de;
45//!
46//! fn main() {
47//! // create a self adaptive DE with an inital search area
48//! // from -10 to 10 in 5 dimensions.
49//! let mut de = self_adaptive_de(vec![(-10.0, 10.0); 5], |pos| {
50//! // cost function to minimize: sum of squares
51//! pos.iter().fold(0.0, |sum, x| sum + x*x)
52//! });
53//!
54//! // perform 10000 cost evaluations
55//! de.iter().nth(10000);
56//!
57//! // show the result
58//! let (cost, pos) = de.best().unwrap();
59//! println!("cost: {}", cost);
60//! println!("pos: {:?}", pos);
61//! }
62//! ```
63//!
64//! ### Tutorial: Rastrigin
65//!
66//! The population supports an `Iterator` for evaluating. Each call
67//! of `next()` evaluates the cost function and returns the
68//! fitness value of the current global best. This way it is possible
69//! to use all the iterator's features for optimizig. Here are a few
70//! examples.
71//!
72//! Let's say we have the [Rastrigin](https://en.wikipedia.org/wiki/Rastrigin_function)
73//! cost function:
74//!
75//! ```
76//! use std::f32::consts::PI;
77//!
78//! fn rastrigin(pos: &[f32]) -> f32 {
79//! pos.iter().fold(0.0, |sum, x|
80//! sum + x * x - 10.0 * (2.0 * PI * x).cos() + 10.0)
81//! }
82//! ```
83//!
84//! We'd like to search for the minimum in the range -5.12 to 5.12, for
85//! 30 dimensions:
86//!
87//! ```
88//! let initial_min_max = vec![(-5.12, 5.12); 30];
89//! ```
90//!
91//! We can create a self adaptive DE, and search until the cost
92//! reaches a given minimum:
93//!
94//! ```
95//! # use differential_evolution::self_adaptive_de;
96//! # fn rastrigin(pos: &[f32]) -> f32 { 0.0 }
97//! # let initial_min_max = vec![(-5.12, 5.12); 2];
98//! let mut de = self_adaptive_de(initial_min_max, rastrigin);
99//! de.iter().find(|&cost| cost < 0.1);
100//! ```
101//!
102//! This is a bit dangerous though, because the optimizer might never reach that minimum.
103//! It is safer to just let it run for a given number of evaluations:
104//!
105//! ```
106//! # use differential_evolution::self_adaptive_de;
107//! # fn rastrigin(pos: &[f32]) -> f32 { 0.0 }
108//! # let initial_min_max = vec![(-5.0, 5.0); 2];
109//! let mut de = self_adaptive_de(initial_min_max, rastrigin);
110//! de.iter().nth(10000);
111//! ```
112//!
113//! If is possible to do some smart combinations: run until cost is below a threshold, or until
114//! the maximum number of iterations have been reached:
115//!
116//! ```
117//! # use differential_evolution::self_adaptive_de;
118//! # fn sum_of_squares(pos: &[f32]) -> f32 { 0.0 }
119//! # let initial_min_max = vec![(-5.12, 5.12); 2];
120//! let mut de = self_adaptive_de(initial_min_max, sum_of_squares);
121//! de.iter().take(100000).find(|&cost| cost < 0.1);
122//! ```
123//!
124//! When you are finished with iterating, you can extract the best solution found so far with
125//! `de.best()`. This retrieves the minimum cost and the position vector that has lead to this
126//! cost:
127//!
128//! ```
129//! # use differential_evolution::self_adaptive_de;
130//! # fn sum_of_squares(pos: &[f32]) -> f32 { 0.0 }
131//! # let initial_min_max = vec![(-5.12, 5.12); 2];
132//! # let mut de = self_adaptive_de(initial_min_max, sum_of_squares);
133//! # de.iter().nth(1000);
134//! let (cost, pos) = de.best().unwrap();
135//! println!("{} best cost", cost);
136//! println!("{:?} best position", pos);
137//! ```
138//!
139//! # Similar Crates
140//!
141//! - [darwin-rs](https://github.com/willi-kappler/darwin-rs)
142//! - [Rs Genetic](https://github.com/m-decoster/RsGenetic)
143//!
144
145extern crate rand;
146
147use rand::distributions::{IndependentSample, Range};
148
149/// Holds all settings for the self adaptive differential evolution
150/// algorithm.
151pub struct Settings<F, R>
152 where F: Fn(&[f32]) -> f32,
153 R: rand::Rng
154{
155 /// The population is initialized with uniform random
156 /// for each dimension between the tuple's size.
157 /// Beware that this is only the initial state, the DE
158 /// will search outside of this initial search space.
159 pub min_max_pos: Vec<(f32, f32)>,
160
161 /// Minimum and maximum value for `cr`, the crossover control parameter.
162 /// a good value is (0, 1) so cr is randomly choosen between in the full
163 /// range of usable CR's from `[0, 1)`.
164 pub cr_min_max: (f32, f32),
165
166 /// Probability to change the `cr` value of an individual. Tests with
167 /// 0.05, 0.1, 0.2 and 0.3 did not show any significant different
168 /// results. So 0.1 seems to be a reasonable choice.
169 pub cr_change_probability: f32,
170
171 /// Minimum and maximum value for `f`, the amplification factor of the
172 /// difference vector. DE is more sensitive to `F` than it is to `CR`.
173 /// In literature, `F` is rarely greater than 1. If `F=0`, the evolution
174 /// degenerates to a crossover but no mutation, so a reasonable choise
175 /// for f_min_max seems to be (0.1, 1.0).
176 pub f_min_max: (f32, f32),
177
178 /// Probability to change the `f` value of an individual. See
179 /// `cr_change_probability`, 0.1 is a reasonable choice.
180 pub f_change_probability: f32,
181
182 /// Number of individuals for the DE. In many benchmarks, a size of
183 /// 100 is used. The choice somewhat depends on the difficulty and the
184 /// dimensionality of the problem to solve. Reasonable choices seem
185 /// between 20 and 200.
186 pub pop_size: usize,
187
188 /// Random number generator used to generate mutations. If the fitness
189 /// function is fairly fast, the random number generator should be
190 /// very fast as well. Since it is not necessary to use a cryptographic
191 /// secure RNG, the best (fastest) choice is to use `rand::weak_rng()`.
192 pub rng: R,
193
194 /// The cost function to minimize. This takes an `&[f32]` and returns
195 /// the calculated cost for this position as `f32`. This should be
196 /// fast to evaluate, and always produce the same result for the same
197 /// input.
198 pub cost_function: F,
199}
200
201impl<F> Settings<F, rand::XorShiftRng>
202 where F: Fn(&[f32]) -> f32
203{
204 /// Creates default settings for the differential evolution. It uses the default
205 /// parameters as defined in the paper "Self-Adapting Control Parameters in Differential
206 /// Evolution: A Comparative Study on Numerical Benchmark Problems", with a population
207 /// size of 100. It also uses This uses `rand::weak_rng()` for the fastest random number
208 /// generator available.
209 ///
210 /// For most problems this should be a fairly good parameter set.
211 pub fn default(min_max_pos: Vec<(f32, f32)>,
212 cost_function: F)
213 -> Settings<F, rand::XorShiftRng> {
214 Settings {
215 min_max_pos: min_max_pos,
216
217 cr_min_max: (0.0, 1.0),
218 cr_change_probability: 0.1,
219
220 f_min_max: (0.1, 1.0),
221 f_change_probability: 0.1,
222
223 pop_size: 100,
224 rng: rand::weak_rng(),
225
226 cost_function: cost_function,
227 }
228 }
229}
230
231/// Internally used struct for an inivididual.
232#[derive(Clone)]
233struct Individual {
234 pos: Vec<f32>,
235 // the lower, the better.
236 cost: Option<f32>,
237
238 // control parameters
239 cr: f32,
240 f: f32,
241}
242
243/// Holds the population for the differential evolution based on the given settings.
244pub struct Population<F, R>
245 where F: Fn(&[f32]) -> f32,
246 R: rand::Rng
247{
248 curr: Vec<Individual>,
249 best: Vec<Individual>,
250
251 settings: Settings<F, R>,
252
253 // index of global best individual. Might be in best or in curr.
254 best_idx: Option<usize>,
255
256 // cost value of the global best individual, for quick access
257 best_cost_cache: Option<f32>,
258 num_cost_evaluations: usize,
259
260 dim: usize,
261 between_popsize: Range<usize>,
262 between_dim: Range<usize>,
263 between_cr: Range<f32>,
264 between_f: Range<f32>,
265
266 pop_countdown: usize,
267}
268
269
270/// Convenience function to create a fully configured self adaptive
271/// differential evolution population.
272pub fn self_adaptive_de<F>(min_max_pos: Vec<(f32, f32)>,
273 cost_function: F)
274 -> Population<F, rand::XorShiftRng>
275 where F: Fn(&[f32]) -> f32
276{
277 Population::new(Settings::default(min_max_pos, cost_function))
278}
279
280impl<F, R> Population<F, R>
281 where F: Fn(&[f32]) -> f32,
282 R: rand::Rng
283{
284 /// Creates a new population based on the given settings.
285 pub fn new(s: Settings<F, R>) -> Population<F, R> {
286 assert!(s.min_max_pos.len() >= 1,
287 "need at least one element to optimize");
288
289 // create a vector of randomly initialized individuals for current.
290 let dim = s.min_max_pos.len();
291
292 // Empty individual, with no cost value (yet)
293 let dummy_individual = Individual {
294 pos: vec![0.0; dim],
295 cost: None,
296 cr: 0.0,
297 f: 0.0,
298 };
299
300 // creates all the empty individuals
301 let mut pop = Population {
302 curr: vec![dummy_individual.clone(); s.pop_size],
303 best: vec![dummy_individual; s.pop_size],
304 best_idx: None,
305 best_cost_cache: None,
306 num_cost_evaluations: 0,
307 dim: dim,
308 pop_countdown: s.pop_size,
309 between_popsize: Range::new(0, s.pop_size),
310 between_dim: Range::new(0, dim),
311 between_cr: Range::new(s.cr_min_max.0, s.cr_min_max.1),
312 between_f: Range::new(s.f_min_max.0, s.f_min_max.1),
313 settings: s,
314 };
315
316 for ind in &mut pop.curr {
317 // init control parameters
318 ind.cr = pop.between_cr.ind_sample(&mut pop.settings.rng);
319 ind.f = pop.between_f.ind_sample(&mut pop.settings.rng);
320
321 // random range for each dimension
322 for d in 0..dim {
323 let between_min_max = Range::new(pop.settings.min_max_pos[d].0,
324 pop.settings.min_max_pos[d].1);
325 ind.pos[d] = between_min_max.ind_sample(&mut pop.settings.rng);
326 }
327 }
328
329 pop
330 }
331
332 /// Loops through each individual and updates its personal best.
333 fn update_best(&mut self) {
334 for i in 0..self.curr.len() {
335 let curr = &mut self.curr[i];
336 let best = &mut self.best[i];
337
338 // we use <= here, so that the individual moves even if the cost
339 // stays the same.
340 if best.cost.is_none() || curr.cost.unwrap() <= best.cost.unwrap() {
341 // replace individual's best. swap is *much* faster than clone.
342 std::mem::swap(curr, best);
343 }
344 }
345 }
346
347 // Modifies all the curr positions. This needs a lot of random numbers, so
348 // for a fast cost function it is important to use a fast random number
349 // generator.
350 fn update_positions(&mut self) {
351 let rng = &mut self.settings.rng;
352 for i in 0..self.curr.len() {
353 // sample 3 different individuals
354 let id1 = self.between_popsize.ind_sample(rng);
355
356 let mut id2 = self.between_popsize.ind_sample(rng);
357 while id2 == id1 {
358 id2 = self.between_popsize.ind_sample(rng);
359 }
360
361 let mut id3 = self.between_popsize.ind_sample(rng);
362 while id3 == id1 || id3 == id2 {
363 id3 = self.between_popsize.ind_sample(rng);
364 }
365
366 let curr = &mut self.curr[i];
367 let best = &self.best[i];
368
369 // see "Self-Adapting Control Parameters in Differential Evolution:
370 // A Comparative Study on Numerical Benchmark Problems"
371 if rng.gen::<f32>() < self.settings.cr_change_probability {
372 curr.cr = self.between_cr.ind_sample(rng);
373 } else {
374 curr.cr = best.cr;
375 }
376 if rng.gen::<f32>() < self.settings.f_change_probability {
377 curr.f = self.between_f.ind_sample(rng);
378 } else {
379 curr.f = best.f;
380 }
381
382 let curr_pos = &mut curr.pos;
383 let best_pos = &best.pos;
384 let best1_pos = &self.best[id1].pos;
385 let best2_pos = &self.best[id2].pos;
386 let best3_pos = &self.best[id3].pos;
387
388 let forced_mutation_dim = self.between_dim.ind_sample(rng);
389
390 // This implements the DE/rand/1/bin, the most widely used algorithm.
391 // See "A Comparative Study of Differential Evolution Variants for
392 // Global Optimization (2006)".
393 for d in 0..self.dim {
394 if d == forced_mutation_dim || rng.gen::<f32>() < curr.cr {
395 curr_pos[d] = best3_pos[d] + curr.f * (best1_pos[d] - best2_pos[d]);
396 } else {
397 curr_pos[d] = best_pos[d];
398 }
399 }
400
401 // reset cost, has to be updated by the user.
402 curr.cost = None;
403 }
404 }
405
406
407 /// Gets a tuple of the best cost and best position found so far.
408 pub fn best(&self) -> Option<(f32, &[f32])> {
409 if let Some(bi) = self.best_idx {
410 let curr = &self.curr[bi];
411 let best = &self.best[bi];
412
413 if curr.cost.is_none() {
414 return Some((best.cost.unwrap(), &best.pos));
415 }
416 if best.cost.is_none() {
417 return Some((curr.cost.unwrap(), &curr.pos));
418 }
419 if curr.cost.unwrap() < best.cost.unwrap() {
420 return Some((curr.cost.unwrap(), &curr.pos));
421 }
422 return Some((best.cost.unwrap(), &best.pos));
423 } else {
424 None
425 }
426 }
427
428 /// Gets the total number of times the cost function has been evaluated.
429 pub fn num_cost_evaluations(&self) -> usize {
430 self.num_cost_evaluations
431 }
432
433 /// Performs a single cost evaluation, and updates best positions and
434 /// evolves the population if the whole population has been evaluated.
435 /// Returns the cost value of the current best solution found.
436 pub fn eval(&mut self) -> Option<f32> {
437 if 0 == self.pop_countdown {
438 // if the whole pop has been evaluated, evolve it to update positions.
439 // this also copies curr to best, if better.
440 self.update_best();
441 self.update_positions();
442 self.pop_countdown = self.curr.len();
443 }
444
445 // perform a single fitness evaluation
446 self.pop_countdown -= 1;
447 let curr = &mut self.curr[self.pop_countdown];
448
449 let cost = (self.settings.cost_function)(&curr.pos);
450 curr.cost = Some(cost);
451 self.num_cost_evaluations += 1;
452
453 // see if we have improved the global best
454 if self.best_cost_cache.is_none() || cost < self.best_cost_cache.unwrap() {
455 self.best_cost_cache = Some(cost);
456 self.best_idx = Some(self.pop_countdown);
457 }
458
459 self.best_cost_cache
460 }
461
462
463 /// Gets an iterator for this population. Each call to `next()`
464 /// performs one cost evaluation.
465 pub fn iter(&mut self) -> PopIter<F, R> {
466 PopIter { pop: self }
467 }
468}
469
470
471/// Iterator for the differential evolution, to perform a single cost
472/// evaluation every time `move()` is called.
473pub struct PopIter<'a, F, R>
474 where F: 'a + Fn(&[f32]) -> f32,
475 R: 'a + rand::Rng
476{
477 pop: &'a mut Population<F, R>,
478}
479
480impl<'a, F, R> Iterator for PopIter<'a, F, R>
481 where F: 'a + Fn(&[f32]) -> f32,
482 R: 'a + rand::Rng
483{
484 type Item = f32;
485
486 /// Simply forwards to the population's `eval()`.
487 fn next(&mut self) -> Option<Self::Item> {
488 self.pop.eval()
489 }
490}
491
492#[cfg(test)]
493mod tests {
494 // TODO
495}