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}