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 }