~swaits/differential-evolution

6f03c0b106d6b49b2d7aac026a19a735529f0573 — swaits 17 years ago c194ae5
use normal distribution on initial random individual creation
initial default range tweaked way down
rename "POP" template parameter to "POPSIZE"
added RunOneGeneration() method - generations can now be run one at a time
added SetRange(i,min,max) method
added a decent amount of comments through main algorithm and class declaration
5 files changed, 270 insertions(+), 116 deletions(-)

M de.h
M de.inl
M derand.h
M derand.inl
A piecewiseapprox.cpp
M de.h => de.h +41 -31
@@ 8,9 8,8 @@

namespace DE
{

	
	template <unsigned int DIM, unsigned int POP=(DIM*10)>
	template <unsigned int DIM, unsigned int POPSIZE=(DIM*10)>
	class Engine
	{
	public:


@@ 18,58 17,69 @@ namespace DE
		Engine();
		virtual ~Engine();

		void Reset();

		const double* GetBest() const;

		void SetRange(double minimum, double maximum);
		void SetRange(unsigned int i, double minimum, double maximum);
		void SetRange(const Vector<DIM>& minimum, const Vector<DIM>& maximum);
		
		virtual double CalculateError(const double testcase[DIM], bool& stop) = 0;

		void Reset();
		bool Solve(unsigned int maxgenerations = 1000000000);
		bool RunOneGeneration();

	private:

		void Select(unsigned int candidate, unsigned int *r1 = 0, unsigned int *r2 = 0, unsigned int *r3 = 0, unsigned int *r4 = 0, unsigned int *r5 = 0);

		// randomly select unique individuals
		void Select(
			unsigned int candidate,
			unsigned int *r1 = 0,
			unsigned int *r2 = 0,
			unsigned int *r3 = 0,
			unsigned int *r4 = 0,
			unsigned int *r5 = 0
		);

		// print a single individual to stdout
		void Dump(const double& fitness, Vector<DIM>& individual) const;

		void MakeTrial_best1exp(unsigned int candidate, Vector<DIM>& trial);
		void MakeTrial_rand1exp(unsigned int candidate, Vector<DIM>& trial);
		void MakeTrial_randtobest1exp(unsigned int candidate, Vector<DIM>& trial);
		void MakeTrial_best2exp(unsigned int candidate, Vector<DIM>& trial);
		void MakeTrial_rand2exp(unsigned int candidate, Vector<DIM>& trial);
		void MakeTrial_best1bin(unsigned int candidate, Vector<DIM>& trial);
		void MakeTrial_rand1bin(unsigned int candidate, Vector<DIM>& trial);
		void MakeTrial_randtobest1bin(unsigned int candidate, Vector<DIM>& trial);
		void MakeTrial_best2bin(unsigned int candidate, Vector<DIM>& trial);
		void MakeTrial_rand2bin(unsigned int candidate, Vector<DIM>& trial);

	private:

		// DE algorithm
		void MakeTrial_best1exp       (unsigned int candidate, Vector<DIM>& trial);
		void MakeTrial_rand1exp       (unsigned int candidate, Vector<DIM>& trial);
		void MakeTrial_randtobest1exp (unsigned int candidate, Vector<DIM>& trial);
		void MakeTrial_best2exp       (unsigned int candidate, Vector<DIM>& trial);
		void MakeTrial_rand2exp       (unsigned int candidate, Vector<DIM>& trial);
		void MakeTrial_best1bin       (unsigned int candidate, Vector<DIM>& trial);
		void MakeTrial_rand1bin       (unsigned int candidate, Vector<DIM>& trial);
		void MakeTrial_randtobest1bin (unsigned int candidate, Vector<DIM>& trial);
		void MakeTrial_best2bin       (unsigned int candidate, Vector<DIM>& trial);
		void MakeTrial_rand2bin       (unsigned int candidate, Vector<DIM>& trial);

		// constants, parameters
		static const double DEFAULTSCALE;
		static const double DEFAULTCROSSOVER;
		static const double DEFAULTRANGE;
		static const double BIGDOUBLE;
		double              scale;
		double              crossover;

		// entropy source
		Random prng;

	private:

		Random      prng;

		// population
		Vector<DIM> best;
		Vector<DIM> minimum;
		Vector<DIM> maximum;
		Vector<DIM> population[POP];
		double      fitness[POP];

		double      scale;
		double      crossover;
		Vector<DIM> mean;
		Vector<DIM> variance;
		Vector<DIM> population[POPSIZE];
		double      fitness[POPSIZE];
		double      bestfitness;

	};
		// algorithm state
		bool         success;
		unsigned int generation;

	};

};


M de.inl => de.inl +127 -85
@@ 10,43 10,44 @@
#endif


template <unsigned int DIM, unsigned int POP>
const double DE::Engine<DIM,POP>::DEFAULTSCALE = 0.85;
template <unsigned int DIM, unsigned int POPSIZE>
const double DE::Engine<DIM,POPSIZE>::DEFAULTSCALE = 0.85;

template <unsigned int DIM, unsigned int POP>
const double DE::Engine<DIM,POP>::DEFAULTCROSSOVER = 1.0;
template <unsigned int DIM, unsigned int POPSIZE>
const double DE::Engine<DIM,POPSIZE>::DEFAULTCROSSOVER = 1.0;

template <unsigned int DIM, unsigned int POP>
const double DE::Engine<DIM,POP>::DEFAULTRANGE= 100.0;
template <unsigned int DIM, unsigned int POPSIZE>
const double DE::Engine<DIM,POPSIZE>::DEFAULTRANGE= 0.1;

template <unsigned int DIM, unsigned int POP>
const double DE::Engine<DIM,POP>::BIGDOUBLE = 1.79e308; // close to max double
template <unsigned int DIM, unsigned int POPSIZE>
const double DE::Engine<DIM,POPSIZE>::BIGDOUBLE = 1.79e308; // close to max double

template <unsigned int DIM, unsigned int POP>
inline DE::Engine<DIM,POP>::Engine()
template <unsigned int DIM, unsigned int POPSIZE>
inline DE::Engine<DIM,POPSIZE>::Engine()
{
	// set defaults
	scale     = DEFAULTSCALE;
	crossover = DEFAULTCROSSOVER;

	for ( unsigned int i=0;i<DIM;i++ )
	for ( unsigned int i=0;i<DIM;++i )
	{
		this->minimum[i] = -DEFAULTRANGE;
		this->maximum[i] = -(this->minimum[i]);
		this->mean[i]     = 0.0;
		this->variance[i] = DEFAULTRANGE;
	}

	// create an initial population
	Reset();

	return;
}

template <unsigned int DIM, unsigned int POP>
inline DE::Engine<DIM,POP>::~Engine()
template <unsigned int DIM, unsigned int POPSIZE>
inline DE::Engine<DIM,POPSIZE>::~Engine()
{
	return;
}

template <unsigned int DIM, unsigned int POP>
inline void DE::Engine<DIM,POP>::Reset()
template <unsigned int DIM, unsigned int POPSIZE>
inline void DE::Engine<DIM,POPSIZE>::Reset()
{
	// reset our best
	bestfitness = BIGDOUBLE;


@@ 56,7 57,7 @@ inline void DE::Engine<DIM,POP>::Reset()
	}

	// reset all vectors
	for ( unsigned int i=0;i<POP;i++ )
	for ( unsigned int i=0;i<POPSIZE;i++ )
	{
		// energy tied for worst
		fitness[i] = bestfitness;


@@ 64,48 65,72 @@ inline void DE::Engine<DIM,POP>::Reset()
		// make a new individual
		for ( unsigned int j=0;j<DIM;j++ )
		{
			population[i][j] = prng.RandDouble(minimum[j],maximum[j]);
			population[i][j] = prng.RandGaussian(mean[j],variance[j]);
		}
	}

	// reset state
	generation = 0;
	success    = false;

	return;
}

template <unsigned int DIM, unsigned int POP>
inline void DE::Engine<DIM,POP>::SetRange(double minimum, double maximum)
template <unsigned int DIM, unsigned int POPSIZE>
inline void DE::Engine<DIM,POPSIZE>::SetRange(double minimum, double maximum)
{
	for (unsigned int i=0;i<DIM;i++)
	{
		this->minimum[i] = minimum;
		this->maximum[i] = maximum;
		SetRange(i,minimum,maximum);
	}

	return;
}

template <unsigned int DIM, unsigned int POPSIZE>
inline void DE::Engine<DIM,POPSIZE>::SetRange(unsigned int i, double minimum, double maximum)
{
	if (i<DIM)
	{
		this->mean[i]     = (maximum+minimum)/2.0;
		this->variance[i] = (maximum-minimum)/2.0;
	}

	return;
}

template <unsigned int DIM, unsigned int POP>
inline void DE::Engine<DIM,POP>::SetRange(const Vector<DIM>& minimum, const Vector<DIM>& maximum)
template <unsigned int DIM, unsigned int POPSIZE>
inline void DE::Engine<DIM,POPSIZE>::SetRange(const Vector<DIM>& minimum, const Vector<DIM>& maximum)
{
	this->minimum = minimum;
	this->maximum = maximum;
	for (unsigned int i=0;i<DIM;i++)
	{
		SetRange(i,minimum[i],maximum[i]);
	}

	return;
}

template <unsigned int DIM, unsigned int POP>
inline const double* DE::Engine<DIM,POP>::GetBest() const
template <unsigned int DIM, unsigned int POPSIZE>
inline const double* DE::Engine<DIM,POPSIZE>::GetBest() const
{
	return best;
}

template <unsigned int DIM, unsigned int POP>
inline void DE::Engine<DIM,POP>::Select(unsigned int candidate, unsigned int *r1, unsigned int *r2, unsigned int *r3, unsigned int *r4, unsigned int *r5)
template <unsigned int DIM, unsigned int POPSIZE>
inline void DE::Engine<DIM,POPSIZE>::Select(
	unsigned int candidate,
	unsigned int *r1,
	unsigned int *r2,
	unsigned int *r3,
	unsigned int *r4,
	unsigned int *r5
)
{
	if ( r1 )
	{
		do
		{
			*r1 = prng.RandU32(0,POP-1);
			*r1 = prng.RandU32(0,POPSIZE-1);
		}
		while ( *r1 == candidate );
	}


@@ 114,7 139,7 @@ inline void DE::Engine<DIM,POP>::Select(unsigned int candidate, unsigned int *r1
	{
		do
		{
			*r2 = prng.RandU32(0,POP-1);
			*r2 = prng.RandU32(0,POPSIZE-1);
		}
		while ( (*r2 == candidate) || (*r2 == *r1) );
	}


@@ 123,7 148,7 @@ inline void DE::Engine<DIM,POP>::Select(unsigned int candidate, unsigned int *r1
	{
		do
		{
			*r3 = prng.RandU32(0,POP-1);
			*r3 = prng.RandU32(0,POPSIZE-1);
		}
		while ( (*r3 == candidate) || (*r3 == *r2) || (*r3 == *r1) );
	}


@@ 132,7 157,7 @@ inline void DE::Engine<DIM,POP>::Select(unsigned int candidate, unsigned int *r1
	{
		do
		{
			*r4 = prng.RandU32(0,POP-1);
			*r4 = prng.RandU32(0,POPSIZE-1);
		}
		while ( (*r4 == candidate) || (*r4 == *r3) || (*r4 == *r2) || (*r4 == *r1) );
	}


@@ 141,7 166,7 @@ inline void DE::Engine<DIM,POP>::Select(unsigned int candidate, unsigned int *r1
	{
		do
		{
			*r5 = prng.RandU32(0,POP-1);
			*r5 = prng.RandU32(0,POPSIZE-1);
		}
		while ( (*r5 == candidate) || (*r5 == *r4) || (*r5 == *r3) || (*r5 == *r2) || (*r5 == *r1) );
	}


@@ 149,49 174,66 @@ inline void DE::Engine<DIM,POP>::Select(unsigned int candidate, unsigned int *r1
	return;
}

// TODO: change this to run one generation at a time???
template <unsigned int DIM, unsigned int POP>
inline bool DE::Engine<DIM,POP>::Solve(unsigned int maxgenerations)
template <unsigned int DIM, unsigned int POPSIZE>
inline bool DE::Engine<DIM,POPSIZE>::Solve(unsigned int maxgenerations)
{
	bool success = false;

	// spawn a new generation
	Reset();

	for (unsigned int generation=0;generation<maxgenerations && !success;generation++)
	// run generations until max or success
	while(!success && generation<maxgenerations)
	{
		printf("====== GENERATION %07d =============================================\n\n",generation);
		success = RunOneGeneration();
	}

		for (int candidate=0;candidate<POP && !success;candidate++)
		{
			Vector<DIM> trial;
			MakeTrial_randtobest1exp(candidate,trial);
	// output
	printf("====== RUN COMPLETE       =============================================\n\n");
	Dump(bestfitness,best);

	// finished
	return success;
}

template <unsigned int DIM, unsigned int POPSIZE>
bool DE::Engine<DIM,POPSIZE>::RunOneGeneration()
{
	printf("====== GENERATION %07d =============================================\n\n",++generation);

			double trialfitness = CalculateError(trial,success);
	// attempt to improve each individual in the population
	for (int candidate=0;candidate<POPSIZE && !success;candidate++)
	{
		// create a new trial individual to test
		Vector<DIM> trial;
		MakeTrial_randtobest1exp(candidate,trial);

			if ( trialfitness < fitness[candidate] )
		// determine fitness of trial individual
		double trialfitness = CalculateError(trial,success);

		// see if the trial improved current candidate in population
		if ( trialfitness < fitness[candidate] )
		{
			// it did improve, save it
			fitness[candidate]    = trialfitness;
			population[candidate] = trial;

			// see if it also improved upon our current best
			if ( trialfitness < bestfitness )
			{
				fitness[candidate]    = trialfitness;
				population[candidate] = trial;

				if ( trialfitness < bestfitness )
				{
					bestfitness = trialfitness;
					best        = trial;
					
					Dump(bestfitness,best);
				}
				// it's better than the best, save it
				bestfitness = trialfitness;
				best        = trial;

				// output new best
				Dump(bestfitness,best);
			}
		}
	}

	printf("====== RUN COMPLETE       =============================================\n\n");
	Dump(bestfitness,best);

	return success;
}

template <unsigned int DIM, unsigned int POP>
inline void DE::Engine<DIM,POP>::Dump(const double& fitness, DE::Vector<DIM>& individual) const
template <unsigned int DIM, unsigned int POPSIZE>
inline void DE::Engine<DIM,POPSIZE>::Dump(const double& fitness, DE::Vector<DIM>& individual) const
{
	printf(" fitness = %24.16f\n",fitness);
	for (unsigned int i=0;i<DIM;i++)


@@ 201,8 243,8 @@ inline void DE::Engine<DIM,POP>::Dump(const double& fitness, DE::Vector<DIM>& in
	printf("\n");
}

template <unsigned int DIM, unsigned int POP>
inline void DE::Engine<DIM,POP>::MakeTrial_best1exp(unsigned int candidate, DE::Vector<DIM>& trial)
template <unsigned int DIM, unsigned int POPSIZE>
inline void DE::Engine<DIM,POPSIZE>::MakeTrial_best1exp(unsigned int candidate, DE::Vector<DIM>& trial)
{
	unsigned int r1, r2;
	unsigned int n;


@@ 220,8 262,8 @@ inline void DE::Engine<DIM,POP>::MakeTrial_best1exp(unsigned int candidate, DE::
	return;
}

template <unsigned int DIM, unsigned int POP>
inline void DE::Engine<DIM,POP>::MakeTrial_rand1exp(unsigned int candidate, DE::Vector<DIM>& trial)
template <unsigned int DIM, unsigned int POPSIZE>
inline void DE::Engine<DIM,POPSIZE>::MakeTrial_rand1exp(unsigned int candidate, DE::Vector<DIM>& trial)
{
	unsigned int r1, r2, r3;
	unsigned int n;


@@ 239,8 281,8 @@ inline void DE::Engine<DIM,POP>::MakeTrial_rand1exp(unsigned int candidate, DE::
	return;
}

template <unsigned int DIM, unsigned int POP>
inline void DE::Engine<DIM,POP>::MakeTrial_randtobest1exp(unsigned int candidate, DE::Vector<DIM>& trial)
template <unsigned int DIM, unsigned int POPSIZE>
inline void DE::Engine<DIM,POPSIZE>::MakeTrial_randtobest1exp(unsigned int candidate, DE::Vector<DIM>& trial)
{
	unsigned int r1, r2;
	unsigned int n;


@@ 258,8 300,8 @@ inline void DE::Engine<DIM,POP>::MakeTrial_randtobest1exp(unsigned int candidate
	return;
}

template <unsigned int DIM, unsigned int POP>
inline void DE::Engine<DIM,POP>::MakeTrial_best2exp(unsigned int candidate, DE::Vector<DIM>& trial)
template <unsigned int DIM, unsigned int POPSIZE>
inline void DE::Engine<DIM,POPSIZE>::MakeTrial_best2exp(unsigned int candidate, DE::Vector<DIM>& trial)
{
	unsigned int r1, r2, r3, r4;
	unsigned int n;


@@ 277,8 319,8 @@ inline void DE::Engine<DIM,POP>::MakeTrial_best2exp(unsigned int candidate, DE::
	return;
}

template <unsigned int DIM, unsigned int POP>
inline void DE::Engine<DIM,POP>::MakeTrial_rand2exp(unsigned int candidate, DE::Vector<DIM>& trial)
template <unsigned int DIM, unsigned int POPSIZE>
inline void DE::Engine<DIM,POPSIZE>::MakeTrial_rand2exp(unsigned int candidate, DE::Vector<DIM>& trial)
{
	unsigned int r1, r2, r3, r4, r5;
	unsigned int n;


@@ 296,8 338,8 @@ inline void DE::Engine<DIM,POP>::MakeTrial_rand2exp(unsigned int candidate, DE::
	return;
}

template <unsigned int DIM, unsigned int POP>
inline void DE::Engine<DIM,POP>::MakeTrial_best1bin(unsigned int candidate, DE::Vector<DIM>& trial)
template <unsigned int DIM, unsigned int POPSIZE>
inline void DE::Engine<DIM,POPSIZE>::MakeTrial_best1bin(unsigned int candidate, DE::Vector<DIM>& trial)
{
	unsigned int r1, r2;
	unsigned int n;


@@ 318,8 360,8 @@ inline void DE::Engine<DIM,POP>::MakeTrial_best1bin(unsigned int candidate, DE::
	return;
}

template <unsigned int DIM, unsigned int POP>
inline void DE::Engine<DIM,POP>::MakeTrial_rand1bin(unsigned int candidate, DE::Vector<DIM>& trial)
template <unsigned int DIM, unsigned int POPSIZE>
inline void DE::Engine<DIM,POPSIZE>::MakeTrial_rand1bin(unsigned int candidate, DE::Vector<DIM>& trial)
{
	unsigned int r1, r2, r3;
	unsigned int n;


@@ 340,8 382,8 @@ inline void DE::Engine<DIM,POP>::MakeTrial_rand1bin(unsigned int candidate, DE::
	return;
}

template <unsigned int DIM, unsigned int POP>
inline void DE::Engine<DIM,POP>::MakeTrial_randtobest1bin(unsigned int candidate, DE::Vector<DIM>& trial)
template <unsigned int DIM, unsigned int POPSIZE>
inline void DE::Engine<DIM,POPSIZE>::MakeTrial_randtobest1bin(unsigned int candidate, DE::Vector<DIM>& trial)
{
	unsigned int r1, r2;
	unsigned int n;


@@ 362,8 404,8 @@ inline void DE::Engine<DIM,POP>::MakeTrial_randtobest1bin(unsigned int candidate
	return;
}

template <unsigned int DIM, unsigned int POP>
inline void DE::Engine<DIM,POP>::MakeTrial_best2bin(unsigned int candidate, DE::Vector<DIM>& trial)
template <unsigned int DIM, unsigned int POPSIZE>
inline void DE::Engine<DIM,POPSIZE>::MakeTrial_best2bin(unsigned int candidate, DE::Vector<DIM>& trial)
{
	unsigned int r1, r2, r3, r4;
	unsigned int n;


@@ 384,8 426,8 @@ inline void DE::Engine<DIM,POP>::MakeTrial_best2bin(unsigned int candidate, DE::
	return;
}

template <unsigned int DIM, unsigned int POP>
inline void DE::Engine<DIM,POP>::MakeTrial_rand2bin(unsigned int candidate, DE::Vector<DIM>& trial)
template <unsigned int DIM, unsigned int POPSIZE>
inline void DE::Engine<DIM,POPSIZE>::MakeTrial_rand2bin(unsigned int candidate, DE::Vector<DIM>& trial)
{
	unsigned int r1, r2, r3, r4, r5;
	unsigned int n;

M derand.h => derand.h +2 -0
@@ 25,6 25,8 @@ namespace DE
	
		double RandDouble();
		double RandDouble(double min, double max);

		double RandGaussian(double mean, double variance);
	
		u32 RandU32();
		u32 RandU32(u32 min, u32 max);

M derand.inl => derand.inl +10 -0
@@ 50,6 50,16 @@ inline double DE::Random::RandDouble(double min, double max)
	return ( RandDouble() * (max - min) + min );
}

inline double DE::Random::RandGaussian(double mean, double variance)
{
	double sum = 0.0;
	for (int i=0;i<12;++i)
	{
		sum += RandDouble();
	}
	return mean + ( variance * ( sum - 6.0 ) );
}

inline DE::u32 DE::Random::RandU32()
{
	return Rand();

A piecewiseapprox.cpp => piecewiseapprox.cpp +90 -0
@@ 0,0 1,90 @@
#include <cstdio>
#include <cmath>

#include "de.h"

// the order of the polynomial (actually the number of coefficients, i.e. 1 = constant, 2 = line, etc.)
#define NUM_COEFFICIENTS 7

// the function we're searching for
double TargetFunction(double x)
{
	//return 1.0 / (1.0 + exp(-4.9*x));               // Ken Stanley's NEAT activation
	return (1.0 / (1.0 + exp(-4.9*x)) - 0.5) * 2.0; // " " (signed version)
	//return (1.0 / (1.0 + exp(-x)) - 0.5) * 2.0;     // signed sigmoid
	//return exp(-x*x);                               // gaussian
	//return tanh(x);                                 // tanh
	//return (2.0/(1.0+exp(-x))-1.0);                 // tanh approximation
	//return (x<0.0 ? -1.0 : 1.0) * sqrt(abs(x));     // signed root
	//return sin(x);                                  // sin
}


// a helper function used in calculating the polynomials
double CalculatePiecewise(double x, int n_coeffs, const double coeffs[])
{
	double abs_x = (x < 0.0 ? -x : x);
	const double* pcoeffs;
	if ( abs_x < coeffs[6] )
		pcoeffs = &coeffs[0];
	else
		pcoeffs = &coeffs[3];

	return (pcoeffs[0] + pcoeffs[1]*abs_x + pcoeffs[2]*abs_x*abs_x) * (x < 0.0 ? -1.0 : 1.0);
}


// our DE implementation
class PolySearch: public DE::Engine<NUM_COEFFICIENTS>
{
	public:
		// this method is called by the DE::Engine::Solve() to test a single DE individual
		double CalculateError(const double testcase[NUM_COEFFICIENTS], bool& stop)
		{
			double       error      = 0.0;
			unsigned int test_count = 0;

			// accumulate squared error
			for ( double i=-2.5;i<=2.5;i+=0.01 )
			{
				// test
				double test   = CalculatePiecewise(i,NUM_COEFFICIENTS,testcase);
				double actual = TargetFunction(i);

				// accumulate error^2
				double e = test - actual;
				error += e * e;

				// inc test count (divisor to get from SE to MSE)
				test_count++;
			}

			// convert SE to RMSE
			error = sqrt(error / (double)test_count);

			// set stop flag if the error is really low
			if ( error < 0.0000001 )
			{
				stop = true;
			}

			// done
			return error;
		}
};


// main program
int main()
{
	PolySearch de;

	// setup, adjust from default range
	//de.SetRange(-0.1,0.1);

	// reset, and run the DE
	de.Solve(1000);

	// done
	return 0;
}