1 module minimized;
2 
3 import std.algorithm : swapAt;
4 import std.math : isNaN, approxEqual;
5 import std.range : front, popFront, isRandomAccessRange;
6 import std.random : uniform, rndGen, isUniformRNG;
7 import std.traits : isCallable;
8 import std.functional : toDelegate;
9 import std.exception : enforce;
10 
11 version( unittest )
12 {
13     import std.math : pow;
14     import std.stdio : writeln;
15     import std.algorithm : equal;
16 }
17 
18 private void partialShuffle(Range, RandomGen)(Range r, in size_t n, ref RandomGen gen)
19     if(isRandomAccessRange!Range && isUniformRNG!RandomGen)
20 {
21     enforce(n <= r.length, "n must be <= r.length for partialShuffle.");
22     foreach (i; 0 .. n)
23     {
24         swapAt(r, i, i + uniform(0, r.length - i, gen));
25     }
26 }
27 
28 private void partialShuffle(Range)(Range r, in size_t n)
29     if(isRandomAccessRange!Range)
30 {
31     return partialShuffle(r, n, rndGen);
32 }
33 
34 private struct Individual( RANGE )
35 {
36     double temperature;
37     RANGE parameters;
38 
39     /// Holds f and cr control parameters;
40     double[] evolutionControl;
41 }
42 
43 class DifferentialEvolution( RANGE )
44 {
45     double tau1 = 0.1; /// Tweaking parameter, see Brest et al for details
46     double tau2 = 0.1; /// Tweaking parameter, see Brest et al for details
47 
48     @property void temperatureFunction(F)(auto ref F fp) 
49         if (isCallable!F)
50     {
51         _temperatureFunction = toDelegate!F(fp);
52     }
53 
54     double temperatureFunction( RANGE parameters )
55     {
56         return _temperatureFunction( parameters );
57     }
58 
59     @property void randomIndividual(F)(auto ref F fp) 
60         if (isCallable!F)
61     {
62         _randomIndividual = toDelegate!F(fp);
63     }
64 
65     RANGE randomIndividual()
66     {
67         return _randomIndividual();
68     }
69 
70     void initialize()
71     {
72         foreach( i; 0..(10*randomIndividual().length) )
73         {
74             auto pars = randomIndividual();
75             auto individual = Individual!RANGE( temperatureFunction( pars ),
76                     pars );
77 
78             individual.evolutionControl = [uniform( 0.1, 1.2 ),
79                 uniform( 0.0, 1.0 )];
80 
81             population ~= individual;
82 
83             if ( bestFit.temperature.isNaN || bestFit.temperature >= individual.temperature )
84                 bestFit = individual;
85         }
86 
87     }
88 
89     /// Perform one minimization step (generation)
90     ///
91     /// Return true if improvement was made
92     bool step()
93     {
94         if (population.length == 0)
95             initialize;
96         Individual!RANGE[] processed;
97         Individual!RANGE[] toProcess = population;
98         bool anyAccepted = false;
99         foreach( _; 0..toProcess.length )
100         {
101             auto x = toProcess.front;
102             toProcess.popFront;
103 
104             auto abc = (processed ~ toProcess);
105 
106             abc.partialShuffle( 3 );
107 
108             // uniform int has by default [) borders, so can use length
109             size_t chosenR = uniform( 0, x.parameters.length );
110 
111             Individual!RANGE y;
112             if (uniform(0.0,1.0)<tau1)
113                 y.evolutionControl ~= uniform(0.1,1.2);
114             else
115                 y.evolutionControl ~= x.evolutionControl[0];
116             if (uniform(0.0,1.0)<tau2)
117                 y.evolutionControl ~= uniform(0.0,1.0);
118             else
119                 y.evolutionControl ~= x.evolutionControl[1];
120 
121 
122 
123             foreach( i; 0..(x.parameters.length ) )
124             {
125                 if ( i == chosenR 
126                         || uniform(0.0,1.0) < y.evolutionControl[1] )
127                     y.parameters ~= abc[0].parameters[i]
128                         + y.evolutionControl[0]
129                         *( abc[1].parameters[i] 
130                                 - abc[2].parameters[i] );
131                 else
132                     y.parameters ~= x.parameters[i];
133             }
134             y.temperature = temperatureFunction( y.parameters );
135 
136             if ( y.temperature < x.temperature )
137             {
138                 anyAccepted = true;
139                 processed ~= y;
140                 if (y.temperature < bestFit.temperature)
141                     bestFit = y;
142             } else {
143                 processed ~= x;
144             }
145         }
146 
147         if (anyAccepted) 
148         {
149             population = processed;
150         }
151         return anyAccepted;
152     }
153 
154     /// Return the current best fitting parameters
155     RANGE currentBestFit()
156     {
157         return bestFit.parameters;
158     }
159 
160     RANGE minimize() 
161     {
162 
163         size_t sinceAccepted = 0;
164 
165         while( sinceAccepted < 10 )
166         {
167             if (step())
168             {
169                 sinceAccepted = 0;
170             } else {
171                 sinceAccepted++;
172             }
173         }
174 
175         return bestFit.parameters;
176     }
177 
178     private:
179         Individual!RANGE[] population;
180 
181         double delegate( RANGE parameters ) _temperatureFunction;
182         RANGE delegate() _randomIndividual;
183         Individual!RANGE bestFit; /// Current bestFit found
184 }
185 
186 ///
187 unittest
188 {
189     // Function to minimize
190     auto fn = ( double[] xs ) {
191         auto p = [ 1.0, 2.0, 10, 20, 30 ];
192         return p[2] * (xs[0] - p[0]) * (xs[0] - p[0]) +
193             p[3] * (xs[1] - p[1]) * (xs[1] - p[1]) + p[4];
194     };
195 
196     // Function which will create random initial sets of parameters 
197     auto initFunction = ()
198     {
199         return [ uniform( 0.0, 10.0 ), uniform( 0.0, 10.0 )];
200     };
201 
202     auto de = new DifferentialEvolution!(double[])();
203     de.temperatureFunction = fn;
204     de.randomIndividual = initFunction;
205 
206     auto min = de.minimize;
207 
208     assert( equal!approxEqual( min, [ 1, 2 ] ) );
209 }
210 
211 unittest
212 {
213     // Function to minimize
214     auto p = [ 1.0, 2.0, 10, 20, 30 ];
215     auto fn = ( double[] xs ) {
216         return p[2] * (xs[0] - p[0]) * (xs[0] - p[0]) +
217             p[3] * (xs[1] - p[1]) * (xs[1] - p[1]) + p[4];
218     };
219 
220     // Function which will create random initial sets of parameters 
221     auto initFunction = delegate double[]()
222     {
223         return [ uniform( 0.0, 10.0 ), uniform( 0.0, 10.0 )];
224     };
225 
226     auto de = new DifferentialEvolution!(double[])();
227     de.temperatureFunction = fn;
228     de.randomIndividual = initFunction;
229 
230     auto min = de.minimize;
231 
232     assert( equal!approxEqual( min, [ 1, 2 ] ) );
233     assert( equal( min, de.currentBestFit ) );
234 }
235 
236 // One dimensional system
237 unittest
238 {
239     // Function to minimize
240     auto fn = ( double[] xs ) {
241         return pow(xs[0],2);
242     };
243 
244     // Function which will create random initial sets of parameters 
245     auto initFunction = ()
246     {
247         return [ uniform( 0.0, 10.0 )];
248     };
249 
250     auto de = new DifferentialEvolution!(double[])();
251     de.temperatureFunction = fn;
252     de.randomIndividual = initFunction;
253 
254     auto min = de.minimize;
255 
256     assert( equal!approxEqual( min, [ 0 ] ) );
257 }