Merge branch 'dev'

This commit is contained in:
Mike 2018-05-01 15:16:44 -07:00
commit 210fe1e785
15 changed files with 2234 additions and 0 deletions

210
Breeder.cpp Normal file
View File

@ -0,0 +1,210 @@
//
#include "BitEvolver/Random.h"
#include "BitEvolver/Chromosome.h"
#include "BitEvolver/Breeder.h"
//
#include <iostream>
//
namespace BitEvolver
{
//
using std::cout;
using std::endl;
//
Breeder::Breeder(std::shared_ptr<Random> _random)
{
//
this->random = _random;
}
//
std::shared_ptr<Chromosome> Breeder::Breed(
std::shared_ptr<Chromosome> mama,
std::shared_ptr<Chromosome> papa,
Enums::CrossoverType crossover_type,
Enums::CrossoverOrder crossover_order,
Enums::CrossoverBounds crossover_bounds,
double crossover_point,
double crossover_point_std,
double mutation_rate
)
{
//
std::shared_ptr<Chromosome>
parent_primary,
parent_secondary,
kiddo
;
// Choose primary / secondary parents
switch( crossover_order )
{
//
case Enums::CrossoverOrder::MamaPapa:
parent_primary = mama;
parent_secondary = papa;
break;
//
case Enums::CrossoverOrder::ByFitness:
if ( mama->GetFitness() > papa->GetFitness() ) {
parent_primary = mama;
parent_secondary = papa;
}
else{
parent_primary = papa;
parent_secondary = mama;
}
break;
}
// Directly copy the primary parent first
kiddo = std::shared_ptr<Chromosome>(new Chromosome(this->random, parent_primary->GetBitCount()));
*kiddo = *parent_primary;
// Apply crossover with the secondary parent
this->ApplyCrossover(
kiddo, parent_secondary,
crossover_type, crossover_bounds, crossover_point, crossover_point_std
);
// Apply mutation
this->Mutate(kiddo, mutation_rate);
// Reset kiddo's fitness
kiddo->ResetFitness();
return kiddo;
}
//
void Breeder::Mutate(std::shared_ptr<Chromosome> chromosome, double mutation_rate)
{
//
int
i,
size
;
//
size = chromosome->GetBitCount();
for ( i=0; i<size; i++ ) {
//
if ( this->random->RollBool(mutation_rate) ) {
chromosome->FlipBit(i);
}
}
//
chromosome->ResetFitness();
}
//
int Breeder::PickRandomCrossoverPoint(
std::shared_ptr<Chromosome> chromosome,
Enums::CrossoverBounds crossover_bounds,
double crossover_point,
double crossover_point_std
)
{
//
int crossover_point_index;
int bit_count;
double random_double;
//
bit_count = chromosome->GetBitCount();
/**
Choose a double between [0.0,1.0] for the crossover point.
Use normal distribution, with the mean and std from the parameters.
That way, there is still randomness to the crossover point,
but it still generally sticks near the chosen point.
*/
random_double = this->random->GetNormal(crossover_point, crossover_point_std);
// Apply to the actual int length
crossover_point_index = floor(random_double * bit_count);
// Loop around to keep in bounds?
if ( crossover_bounds == Enums::CrossoverBounds::Wrap ) {
while ( crossover_point_index < 0 )
{
crossover_point_index += bit_count;
}
while ( crossover_point_index >= bit_count)
{
crossover_point_index -= bit_count;
}
}
else if ( crossover_bounds == Enums::CrossoverBounds::Clip ) {
if ( crossover_point_index < 0 ) {
crossover_point_index = 0;
}
else if ( crossover_point_index >= bit_count ) {
crossover_point_index = bit_count-1;
}
}
else{
throw std::runtime_error("Breeder::PickRandomCrossoverPoint() - Invalid crossover_bounds");
}
return crossover_point_index;
}
//
void Breeder::ApplyCrossover(
std::shared_ptr<Chromosome> kiddo,
std::shared_ptr<Chromosome> parent,
Enums::CrossoverType crossover_type,
Enums::CrossoverBounds crossover_bounds,
double crossover_point,
double crossover_point_std
)
{
//
int
bits_count,
crossover_point_index,
i
;
// Only proceed if using sexual crossover
if (crossover_type != Enums::CrossoverType::Sexual) {
return;
}
// For now, don't crossover unless the bit lengths are identical
bits_count = kiddo->GetBitCount();
if ( parent->GetBitCount() != bits_count ) {
throw std::runtime_error("Breeder::ApplyCrossover() - Parent incompatible with Kiddo (bit lengths don't match)");
}
// Pick random crossover point
crossover_point_index = this->PickRandomCrossoverPoint(kiddo, crossover_bounds, crossover_point, crossover_point_std);
// Begin copying the parent at the crossover point and beyond
// (not before)
for ( i=crossover_point_index; i<bits_count; i++) {
kiddo->SetBit( i, parent->GetBit(i) );
}
//
kiddo->ResetFitness();
}
};

57
Breeder.h Normal file
View File

@ -0,0 +1,57 @@
//
#pragma once
//
namespace BitEvolver
{
//
class Breeder
{
//
public:
//
Breeder(std::shared_ptr<class Random> _random);
//
std::shared_ptr<class Chromosome> Breed(
std::shared_ptr<class Chromosome> mama,
std::shared_ptr<class Chromosome> papa,
Enums::CrossoverType crossover_type,
Enums::CrossoverOrder crossover_order,
Enums::CrossoverBounds crossover_bounds,
double crossover_point,
double crossover_point_std,
double mutation_rate
);
//
void Mutate(std::shared_ptr<class Chromosome> chromosome, double mutation_rate);
//
private:
//
std::shared_ptr<class Random> random;
//
int PickRandomCrossoverPoint(
std::shared_ptr<class Chromosome> chromosome,
Enums::CrossoverBounds crossover_bounds,
double crossover_point,
double crossover_point_std
);
void ApplyCrossover(
std::shared_ptr<class Chromosome> kiddo,
std::shared_ptr<class Chromosome> parent,
Enums::CrossoverType crossover_type,
Enums::CrossoverBounds crossover_bounds,
double crossover_point,
double crossover_point_std
);
};
};

228
Chromosome.cpp Normal file
View File

@ -0,0 +1,228 @@
//
#include "BitEvolver/Chromosome.h"
#include "BitEvolver/Random.h"
//
#include <memory>
#include <string>
#include <sstream>
#include <vector>
#include <mutex>
//
namespace BitEvolver
{
//
using std::string;
using std::to_string;
using std::stringstream;
//
Chromosome::Chromosome(std::shared_ptr<Random> _random, int _bits)
{
//
this->random = _random;
this->SetBitCount(_bits);
//
this->Reset();
}
//
void Chromosome::Reset()
{
//
this->Randomize();
}
//
void Chromosome::Randomize()
{
//
std::unique_lock<std::recursive_mutex> lock(this->modification_mutex);
int i;
//
this->bits.clear();
//
for ( i=0; i<this->bits_count_desired; i++ ) {
this->bits.push_back(this->random->GetInt(0, 1));
}
}
//
void Chromosome::SetBitCount(int count)
{
//
std::unique_lock<std::recursive_mutex> lock(this->modification_mutex);
//
this->bits_count_desired = count;
this->Randomize();
}
//
int Chromosome::GetBitCount()
{
return (int)this->bits.size();
}
//
void Chromosome::FlipBit(int index)
{
//
std::unique_lock<std::recursive_mutex> lock(this->modification_mutex);
//
if ( index >= (int)this->bits.size() ) {
throw std::runtime_error("Chromosome::FlipBit() - Tried to flip out of range bit index: " + to_string(index));
}
//
if ( this->bits[index] ) {
this->bits[index] = false;
}
else{
this->bits[index] = true;
}
}
//
bool Chromosome::GetBit(int index)
{
//
std::unique_lock<std::recursive_mutex> lock(this->modification_mutex);
//
if ( index >= (int)this->bits.size() ) {
throw std::runtime_error("Chromosome::GetBit() - Tried to access out of bounds bit");
}
//
return this->bits[index];
}
//
void Chromosome::SetBit(int index, bool b)
{
//
std::unique_lock<std::recursive_mutex> lock(this->modification_mutex);
//
if ( index >= (int)this->bits.size() ) {
throw std::runtime_error("Chromosome::GetBit() - Tried to access out of bounds bit");
}
//
this->bits[index] = b;
}
//
void Chromosome::ResetFitness()
{
//
this->SetFitness(0.0);
}
//
void Chromosome::SetFitness(double d)
{
//
this->fitness = d;
}
//
void Chromosome::AdjustFitness(double d)
{
//
this->fitness += d;
}
//
double Chromosome::GetFitness()
{
//
return this->fitness;
}
//
void Chromosome::ResetError()
{
//
this->ResetFitness();
}
//
void Chromosome::SetError(double e)
{
//
this->SetFitness(-e);
}
//
void Chromosome::AdjustError(double e)
{
//
this->AdjustFitness(-e);
}
//
double Chromosome::GetError()
{
//
return -this->GetFitness();
}
//
string Chromosome::ToString()
{
//
std::unique_lock<std::recursive_mutex> lock(this->modification_mutex);
stringstream s;
//
for ( bool b : this->bits ) {
//
if ( b ) {
s << "1";
}
else{
s << "0";
}
}
//
return s.str();
}
//
const Chromosome& Chromosome::operator=(const Chromosome& other)
{
//
std::unique_lock<std::recursive_mutex> lock1(this->modification_mutex);
int i;
//
this->random = other.random;
this->bits = other.bits;
this->bits_count_desired = other.bits_count_desired;
this->fitness = other.fitness;
// Copy the bits!
this->bits.clear();
for ( i=0; i<(int)other.bits.size(); i++ ) {
this->bits.push_back(other.bits[i]);
}
return *this;
}
};

83
Chromosome.h Normal file
View File

@ -0,0 +1,83 @@
//
#pragma once
//
#include "BitEvolver/Includes.h"
//
#include <memory>
#include <string>
#include <sstream>
#include <vector>
#include <mutex>
//
namespace BitEvolver
{
//
class Chromosome
{
//
public:
//
Chromosome(std::shared_ptr<class Random> _random, int _bits);
//
void Reset();
void Randomize();
//
void SetBitCount(int count);
int GetBitCount();
//
void FlipBit(int index);
//
bool GetBit(int index);
void SetBit(int index, bool b);
//
void ResetFitness();
void SetFitness(double d);
void AdjustFitness(double d);
double GetFitness();
/**
Error is just inverted fitness
*/
void ResetError();
void SetError(double e);
void AdjustError(double e);
double GetError();
//
std::string ToString();
//
const Chromosome& operator=(const Chromosome& other);
//
private:
// Random number generator
std::shared_ptr<class Random> random;
//
std::vector<bool> bits;
int bits_count_desired;
// Fitness
double fitness;
// Mutexes
std::recursive_mutex modification_mutex;
};
};

9
Defines.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef BITEVOLVER_DEFINES_H
#define BITEVOLVER_DEFINES_H
// Nada for now
#endif

47
Enums.h Normal file
View File

@ -0,0 +1,47 @@
#ifndef BITEVOLVER_ENUMS_H
#define BITEVOLVER_ENUMS_H
//
namespace BitEvolver
{
//
namespace Enums
{
//
enum class CrossoverType
{
//
None,
Sexual
};
//
enum class CrossoverOrder
{
//
MamaPapa,
ByFitness
};
//
enum class CrossoverBounds
{
//
Clip,
Wrap
};
//
enum class ElitismType
{
//
None,
Rate,
Absolute
};
};
};
#endif

14
ForwardDeclarations.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef BITEVOLVER_FORWARD_DECLARATIONS_H
#define BITEVOLVER_FORWARD_DECLARATIONS_H
//
namespace BitEvolver
{
//
class Population;
class Breeder;
class Chromosome;
};
#endif

13
Includes.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef BITEVOLVER_INCLUDES_H
#define BITEVOLVER_INCLUDES_H
//
#include "BitEvolver/Defines.h"
#include "BitEvolver/Enums.h"
#endif

136
Makefile
View File

@ -1 +1,137 @@
# Custom functions
define say
$(info [BitEvolver] $1)
endef
define error
$(error [BitEvolver] $1)
endef
define die
$(call error,$1)
$(exit 1)
endef
# Demand BUILD_DIR and BIN_DIR
ifeq ($(BUILD_DIR),)
$(call die,Please provide BUILD_DIR)
endif
ifeq ($(BIN_DIR),)
$(call die,Please provide BIN_DIR)
endif
#
CC=g++
CFLAGS= -c -std=c++11 -Wall -I..
#
OBJECT_PREFIX=BitEvolver_
#
default: default-say
default: release
default:
$(call say,Default target finished)
default-say:
$(call say,Using default target: release)
#
release: release-say
release: CFLAGS+= -O2
release: build-objects
release:
$(call say,Done building RELEASE)
release-say:
$(call say,Building RELEASE)
#
debug: debug-say
debug: CFLAGS+= -g -g3
debug: build-objects
debug:
$(call say,Done building DEBUG)
debug-say:
$(call say,Building DEBUG)
#
build-objects: \
$(BUILD_DIR)/$(OBJECT_PREFIX)Random.o \
$(BUILD_DIR)/$(OBJECT_PREFIX)Population.o \
$(BUILD_DIR)/$(OBJECT_PREFIX)Breeder.o \
$(BUILD_DIR)/$(OBJECT_PREFIX)RouletteWheel.o \
$(BUILD_DIR)/$(OBJECT_PREFIX)Chromosome.o
$(call say,Done building objects)
# Population.o
$(BUILD_DIR)/$(OBJECT_PREFIX)Population.o: \
Population.h \
Population.cpp \
Defines.h Enums.h Includes.h \
Random.h \
Chromosome.h
$(CC) -o $@ \
Population.cpp \
$(CFLAGS)
$(call say,Built $@)
# Breeder.o
$(BUILD_DIR)/$(OBJECT_PREFIX)Breeder.o: \
Breeder.h \
Breeder.cpp \
Defines.h Enums.h Includes.h \
Random.h \
Chromosome.h
$(CC) -o $@ \
Breeder.cpp \
$(CFLAGS)
$(call say,Built $@)
# RouletteWheel.o
$(BUILD_DIR)/$(OBJECT_PREFIX)RouletteWheel.o: \
RouletteWheel.h \
RouletteWheel.cpp \
Defines.h Enums.h Includes.h \
Random.h
$(CC) -o $@ \
RouletteWheel.cpp \
$(CFLAGS)
$(call say,Built $@)
# Chromosome.o
$(BUILD_DIR)/$(OBJECT_PREFIX)Chromosome.o: \
Chromosome.h \
Chromosome.cpp \
Defines.h Enums.h Includes.h \
Random.h
$(CC) -o $@ \
Chromosome.cpp \
$(CFLAGS)
$(call say,Built $@)
# Random.o
$(BUILD_DIR)/$(OBJECT_PREFIX)Random.o: \
Random.h \
Random.cpp \
Defines.h Enums.h Includes.h
$(CC) -o $@ \
Random.cpp \
$(CFLAGS)
$(call say,Built $@)

793
Population.cpp Normal file
View File

@ -0,0 +1,793 @@
//
#include "BitEvolver/Includes.h"
#include "BitEvolver/Random.h"
#include "BitEvolver/Population.h"
#include "BitEvolver/Breeder.h"
#include "BitEvolver/RouletteWheel.h"
#include "BitEvolver/Chromosome.h"
//
#include <memory>
#include <vector>
#include <mutex>
#include <iostream>
#include <algorithm>
#include <thread>
#include <functional>
#include <string>
#include <sstream>
//
namespace BitEvolver
{
//
using std::string;
using std::stringstream;
using std::cout;
using std::endl;
//
Population::Population()
{
//
this->InitRandomGenerator();
this->InitRouletteWheel();
this->InitBreeder();
//
this->Reset();
}
//
void Population::Reset()
{
//
this->evolution_number = 0;
this->population_size = Population::DEFAULT_POPULATION_SIZE;
//
this->SetElitismType(Population::DEFAULT_ELITISM_TYPE);
this->SetElitismRate(Population::DEFAULT_ELITISM_RATE);
this->SetElitismCount(Population::DEFAULT_ELITISM_COUNT);
//
this->SetCrossoverType(Population::DEFAULT_CROSSOVER_TYPE);
this->SetCrossoverOrder(Population::DEFAULT_CROSSOVER_ORDER);
this->SetCrossoverBounds(Population::DEFAULT_CROSSOVER_BOUNDS);
this->SetCrossoverPoint(Population::DEFAULT_CROSSOVER_POINT);
this->SetCrossoverPointStandardDeviation(Population::DEFAULT_CROSSOVER_POINT_STD);
//
this->SetMutationRate(Population::DEFAULT_MUTATION_RATE);
//
this->RandomizePopulation(this->population_size);
}
//
void Population::ClearPopulation()
{
//
this->chromosomes.clear();
this->evolution_number = 0;
}
//
void Population::InitRandomPopulation(int _population_size, int _bit_length)
{
//
this->population_size = _population_size;
//
this->RandomizePopulation(_bit_length);
}
//
void Population::RandomizePopulation(int _bit_length)
{
//
std::shared_ptr<Chromosome> chromosome;
int i;
//
this->ClearPopulation();
for ( i=0; i<this->population_size; i++ ) {
//
chromosome = std::shared_ptr<Chromosome>(
new Chromosome( this->random, _bit_length )
);
this->chromosomes.push_back(chromosome);
}
}
//
void Population::PopulationChanged()
{
//
this->population_needs_sorting = true;
}
//
std::vector<std::shared_ptr<Chromosome>> Population::GetChromosomes()
{
return this->chromosomes;
}
//
void Population::GetChromosomes(std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>> _chromosomes)
{
//
_chromosomes->clear();
for ( std::shared_ptr<Chromosome> chromosome : this->chromosomes) {
_chromosomes->push_back(chromosome);
}
}
//
std::shared_ptr<Chromosome> Population::GetChampion()
{
//
this->EnsureSortedPopulation();
//
if ( this->chromosomes.size() > 0 ) {
return this->chromosomes[0];
}
return nullptr;
}
//
double Population::GetAverageFitness()
{
return this->GetAverageFitness(this->chromosomes);
}
//
double Population::GetAverageFitness(std::vector<std::shared_ptr<Chromosome>> _chromosomes)
{
//
double fitness_sum;
double fitness_average;
//
fitness_sum = 0;
for ( std::shared_ptr<Chromosome> chromosome : _chromosomes ) {
fitness_sum += chromosome->GetFitness();
}
//
fitness_average = 0;
if ( _chromosomes.size() > 0 ) {
fitness_average = fitness_sum / _chromosomes.size();
}
return fitness_average;
}
//
void Population::SetCrossoverType(Enums::CrossoverType t)
{
//
this->crossover_type = t;
}
//
Enums::CrossoverType Population::GetCrossoverType()
{
//
return this->crossover_type;
}
//
void Population::SetCrossoverOrder(Enums::CrossoverOrder o)
{
//
this->crossover_order = o;
}
//
Enums::CrossoverOrder Population::GetCrossoverOrder()
{
//
return this->crossover_order;
}
//
void Population::SetCrossoverBounds(Enums::CrossoverBounds b)
{
//
this->crossover_bounds = b;
}
//
Enums::CrossoverBounds Population::GetCrossoverBounds()
{
//
return this->crossover_bounds;
}
//
void Population::SetCrossoverPoint(double p)
{
//
this->crossover_point = p;
}
//
double Population::GetCrossoverPoint()
{
//
return this->crossover_point;
}
//
void Population::SetCrossoverPointStandardDeviation(double std)
{
//
this->crossover_point_std = std;
}
//
double Population::GetCrossoverPointStandardDeviation()
{
//
return this->crossover_point_std;
}
//
void Population::SetMutationRate(double r)
{
//
this->mutation_rate = r;
}
//
double Population::GetMutationRate()
{
//
return this->mutation_rate;
}
//
void Population::SetElitismType(Enums::ElitismType t)
{
//
this->elitism_type = t;
}
//
Enums::ElitismType Population::GetElitismType()
{
//
return this->elitism_type;
}
//
void Population::SetElitismRate(double r)
{
//
this->elitism_rate = r;
}
//
double Population::GetElitismRate()
{
//
return this->elitism_rate;
}
//
void Population::SetElitismCount(int c)
{
//
this->elitism_count = c;
}
//
int Population::GetElitismCount()
{
//
return this->elitism_count;
}
//
void Population::EvaluateFitness(std::function<double(std::shared_ptr<Chromosome>)> evaluation_callback)
{
//
std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>> _chromosomes_copy;
std::vector<std::shared_ptr<std::thread>> threads;
std::shared_ptr<std::thread> thread;
int
threads_count,
i
;
// Make a new vector containing all current chromosomes
this->population_modification_mutex.lock();
_chromosomes_copy = std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>>(
new std::vector<std::shared_ptr<Chromosome>>()
);
for ( i=0; i<(int)this->chromosomes.size(); i++ ) {
_chromosomes_copy->push_back( this->chromosomes[i] );
}
this->population_modification_mutex.unlock();
// Spawn threads
threads_count = this->GetThreadCountSuggestion();
for ( i=0; i<threads_count; i++) {
//
thread = std::shared_ptr<std::thread>(
new std::thread(&Population::EvaluateFitness_Thread, this, _chromosomes_copy, evaluation_callback)
);
threads.push_back(thread);
}
// Wait for threads to finish
for ( i=0; i<threads_count; i++ ) {
threads[i]->join();
}
}
//
void Population::EvaluateError(std::function<double(std::shared_ptr<Chromosome>)> evaluation_callback)
{
//
std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>> _chromosomes_copy;
std::vector<std::shared_ptr<std::thread>> threads;
std::shared_ptr<std::thread> thread;
int
threads_count,
i
;
// Make a new vector containing all current chromosomes
this->population_modification_mutex.lock();
_chromosomes_copy = std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>>(
new std::vector<std::shared_ptr<Chromosome>>()
);
for ( i=0; i<(int)this->chromosomes.size(); i++ ) {
_chromosomes_copy->push_back( this->chromosomes[i] );
}
this->population_modification_mutex.unlock();
// Spawn threads
threads_count = this->GetThreadCountSuggestion();
for ( i=0; i<threads_count; i++) {
//
thread = std::shared_ptr<std::thread>(
new std::thread(&Population::EvaluateError_Thread, this, _chromosomes_copy, evaluation_callback)
);
threads.push_back(thread);
}
// Wait for threads to finish
for ( i=0; i<threads_count; i++ ) {
threads[i]->join();
}
}
//
void Population::Evolve()
{
//
std::shared_ptr<std::vector< std::shared_ptr<Chromosome> > > population_new;
//
if ( this->chromosomes.size() == 0 ) {
return;
}
//
this->EnsureSortedPopulation();
//
population_new = std::shared_ptr<
std::vector<
std::shared_ptr<Chromosome>
>
>(
new std::vector<std::shared_ptr<Chromosome>>()
);
// Breed the new population
this->BreedNewPopulation(population_new, (int)this->chromosomes.size());
// Replace old population with the new
this->chromosomes = *population_new;
this->evolution_number++;
//
this->PopulationChanged();
}
//
int Population::GetEvolutionNumber()
{
return this->evolution_number;
}
//
void Population::PrintPopulation()
{
//
this->EnsureSortedPopulation();
this->PrintPopulation(this->chromosomes);
}
//
void Population::PrintPopulation(std::vector<std::shared_ptr<Chromosome>> _chromosomes)
{
//
for ( std::shared_ptr<Chromosome> chromosome : chromosomes ) {
cout << chromosome->ToString() << endl;
}
cout << "Average Fitness --> " << this->GetAverageFitness(_chromosomes) << endl;
}
//
void Population::InitRandomGenerator()
{
//
this->random = std::shared_ptr<Random>(
new Random()
);
}
//
void Population::InitRouletteWheel()
{
//
this->roulette_wheel = std::shared_ptr<RouletteWheel>(
new RouletteWheel()
);
}
//
void Population::InitBreeder()
{
//
if ( !this->random ) {
throw std::runtime_error("Population::InitBreeder() - Should come after InitRandomGenerator()");
}
//
this->breeder = std::shared_ptr<Breeder>(
new Breeder( this->random )
);
}
//
void Population::EnsureSortedPopulation()
{
//
if ( !this->population_needs_sorting ) {
return;
}
// Yay std::sort
std::sort(
this->chromosomes.begin(),
this->chromosomes.end(),
[]( std::shared_ptr<Chromosome>& left, std::shared_ptr<Chromosome>& right ) -> bool
{
//
if ( left->GetFitness() > right->GetFitness() ) {
return true;
}
return false;
}
);
//
this->population_needs_sorting = false;
}
//
void Population::BreedNewPopulation(std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>> population_new, int size)
{
//
std::vector<std::shared_ptr<std::thread>> threads;
std::shared_ptr<std::thread> thread;
int
thread_count,
i
;
// First, populate the roulette wheel
this->roulette_wheel->SetChromosomes(this->chromosomes);
// Next, seed the population with elites
this->SeedPopulationWithElites(population_new);
// Next, breed until we've reached our new size
thread_count = this->GetThreadCountSuggestion();
for ( i=0; i<thread_count; i++) {
thread = std::shared_ptr<std::thread>(
new std::thread(&Population::BreedNewPopulation_Thread, this, population_new, size)
);
threads.push_back(thread);
}
for ( i=0; i<(int)threads.size(); i++) {
threads[i]->join();
}
// Finally, reset the fitness of the new population
for ( i=0; i<(int)population_new->size(); i++ ) {
population_new->at(i)->ResetFitness();
}
}
//
void Population::BreedNewPopulation_Thread(std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>> population_new, int size)
{
//
std::shared_ptr<Chromosome> kiddo;
//
while ( (int)population_new->size() < size )
{
//
kiddo = this->BreedChild();
// Mutexed
this->breed_mutex.lock();
if ( (int)population_new->size() < size ) {
population_new->push_back(kiddo);
}
this->breed_mutex.unlock();
}
}
//
int Population::DetermineEliteCount()
{
//
int count;
//
switch( this->elitism_type )
{
//
default:
case Enums::ElitismType::None:
count = 0;
break;
//
case Enums::ElitismType::Absolute:
count = this->elitism_count;
break;
//
case Enums::ElitismType::Rate:
count = floor( this->chromosomes.size() * this->elitism_rate );
break;
}
return count;
}
//
void Population::SeedPopulationWithElites(std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>> population_new)
{
//
std::unique_lock<std::recursive_mutex> lock(this->population_modification_mutex);
std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>> elites;
std::vector<std::shared_ptr<std::thread>> threads;
std::shared_ptr<std::thread> thread;
int
elites_count,
i
;
// Determine how many elites to copy
elites_count = this->DetermineEliteCount();
elites = std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>>(
new std::vector<std::shared_ptr<Chromosome>>()
);
// First, copy over just the pointers
for ( i=0; i<elites_count && i<(int)this->chromosomes.size(); i++) {
elites->push_back( this->chromosomes[i] );
}
// Then, make them full copies (uses threads)
this->CopyChromosomes(elites, population_new);
}
//
void Population::EvaluateFitness_Thread(
std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>> _chromosomes,
std::function<double(std::shared_ptr<Chromosome>)> evaluation_callback
)
{
//
std::shared_ptr<Chromosome> chromosome;
double fitness;
//
while (true)
{
// Grab a free chromosome
this->evaluate_fitness_mutex.lock();
chromosome = nullptr;
if ( _chromosomes->size() ) {
chromosome = _chromosomes->at(_chromosomes->size()-1);
_chromosomes->pop_back();
}
this->evaluate_fitness_mutex.unlock();
// Call the evaluation callback
if ( chromosome != nullptr ) {
fitness = evaluation_callback(chromosome);
chromosome->SetFitness(fitness);
}
// We're done if there was nothing to grab
else{
break;
}
}
}
//
void Population::EvaluateError_Thread(
std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>> _chromosomes,
std::function<double(std::shared_ptr<Chromosome>)> evaluation_callback
)
{
//
std::shared_ptr<Chromosome> chromosome;
double error;
//
while (true)
{
// Grab a free chromosome
this->evaluate_fitness_mutex.lock();
chromosome = nullptr;
if ( _chromosomes->size() ) {
chromosome = _chromosomes->at(_chromosomes->size()-1);
_chromosomes->pop_back();
}
this->evaluate_fitness_mutex.unlock();
// Call the evaluation callback
if ( chromosome != nullptr ) {
error = evaluation_callback(chromosome);
chromosome->SetError(error);
}
// We're done if there was nothing to grab
else{
break;
}
}
}
//
void Population::CopyChromosomes(
std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>> _chromosomes_source,
std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>> _chromosomes_destination
)
{
//
std::vector<std::shared_ptr<std::thread>> threads;
std::shared_ptr<std::thread> thread;
int
threads_count,
i
;
// Spawn threads
threads_count = this->GetThreadCountSuggestion();
for ( i=0; i<threads_count; i++) {
//
thread = std::shared_ptr<std::thread>(
new std::thread(&Population::CopyChromosomes_Thread, this, _chromosomes_source, _chromosomes_destination)
);
threads.push_back(thread);
}
// Wait for threads to finish
for ( i=0; i<threads_count; i++ ) {
threads[i]->join();
}
}
//
void Population::CopyChromosomes_Thread(
std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>> _chromosomes_source,
std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>> _chromosomes_destination
)
{
//
std::shared_ptr<Chromosome>
chromosome_original,
chromosome_copied
;
stringstream ss;
//
while ( _chromosomes_destination->size() < _chromosomes_source->size() )
{
// Grab the next slot
this->copy_chromosomes_mutex.lock();
chromosome_original = nullptr;
chromosome_copied = nullptr;
if ( _chromosomes_destination->size() < _chromosomes_source->size() ) {
//
chromosome_copied = std::shared_ptr<Chromosome>(
new Chromosome(this->random, 0)
);
_chromosomes_destination->push_back(chromosome_copied);
chromosome_original = _chromosomes_source->at(_chromosomes_destination->size()-1);
}
this->copy_chromosomes_mutex.unlock();
// Make a full copy of the original, outside the lock
if ( chromosome_copied && chromosome_original ) {
*chromosome_copied = *chromosome_original;
}
}
}
//
std::shared_ptr<Chromosome> Population::BreedChild()
{
//
std::shared_ptr<Chromosome>
mama, papa, kiddo
;
// Pick two parents
mama = this->roulette_wheel->Spin();
papa = this->roulette_wheel->Spin();
//
kiddo = this->breeder->Breed(
mama, papa,
this->crossover_type,
this->crossover_order,
this->crossover_bounds,
this->crossover_point,
this->crossover_point_std,
this->mutation_rate
);
return kiddo;
}
//
int Population::GetThreadCountSuggestion()
{
//
int thread_count;
//
thread_count = std::thread::hardware_concurrency();
return thread_count;
}
};

178
Population.h Normal file
View File

@ -0,0 +1,178 @@
//
#pragma once
//
#include "BitEvolver/Includes.h"
//
#include <memory>
#include <vector>
#include <mutex>
#include <functional>
//
namespace BitEvolver
{
//
class Population
{
//
public:
//
Population();
//
void Reset();
//
void ClearPopulation();
void InitRandomPopulation(int _population_size, int _bit_length);
void RandomizePopulation(int _bit_length);
//
void PopulationChanged();
//
std::vector<std::shared_ptr<class Chromosome>> GetChromosomes();
void GetChromosomes(std::shared_ptr<std::vector<std::shared_ptr<class Chromosome>>> _chromosomes);
std::shared_ptr<class Chromosome> GetChampion();
//
double GetAverageFitness();
double GetAverageFitness(std::vector<std::shared_ptr<class Chromosome>> _chromosomes);
//
void SetCrossoverType(Enums::CrossoverType t);
Enums::CrossoverType GetCrossoverType();
void SetCrossoverOrder(Enums::CrossoverOrder o);
Enums::CrossoverOrder GetCrossoverOrder();
void SetCrossoverBounds(Enums::CrossoverBounds b);
Enums::CrossoverBounds GetCrossoverBounds();
void SetCrossoverPoint(double p);
double GetCrossoverPoint();
void SetCrossoverPointStandardDeviation(double std);
double GetCrossoverPointStandardDeviation();
//
void SetMutationRate(double r);
double GetMutationRate();
//
void SetElitismType(Enums::ElitismType t);
Enums::ElitismType GetElitismType();
void SetElitismRate(double r);
double GetElitismRate();
void SetElitismCount(int c);
int GetElitismCount();
//
void EvaluateFitness(std::function<double(std::shared_ptr<Chromosome>)> evaluation_callback);
void EvaluateError(std::function<double(std::shared_ptr<Chromosome>)> evaluation_callback);
//
void Evolve();
int GetEvolutionNumber();
//
void PrintPopulation();
void PrintPopulation(std::vector<std::shared_ptr<class Chromosome>> _chromosomes);
// Constants
const static int DEFAULT_POPULATION_SIZE = 100;
const static Enums::ElitismType DEFAULT_ELITISM_TYPE = Enums::ElitismType::Rate;
constexpr static double DEFAULT_ELITISM_RATE = 0.01;
const static int DEFAULT_ELITISM_COUNT = 1;
constexpr static double DEFAULT_MUTATION_RATE = 0.01;
//
const static Enums::CrossoverType DEFAULT_CROSSOVER_TYPE = Enums::CrossoverType::Sexual;
const static Enums::CrossoverOrder DEFAULT_CROSSOVER_ORDER = Enums::CrossoverOrder::MamaPapa;
const static Enums::CrossoverBounds DEFAULT_CROSSOVER_BOUNDS = Enums::CrossoverBounds::Wrap;
constexpr static double DEFAULT_CROSSOVER_POINT = 0.7;
constexpr static double DEFAULT_CROSSOVER_POINT_STD = 0.25;
//
private:
//
std::shared_ptr<class Random> random;
std::shared_ptr<class Breeder> breeder;
//
std::vector<std::shared_ptr<class Chromosome>> chromosomes;
int population_size;
bool population_needs_sorting;
int evolution_number;
//
Enums::CrossoverType crossover_type;
Enums::CrossoverOrder crossover_order;
Enums::CrossoverBounds crossover_bounds;
double crossover_point;
double crossover_point_std;
double mutation_rate;
Enums::ElitismType elitism_type;
double elitism_rate;
int elitism_count;
//
std::shared_ptr<class RouletteWheel> roulette_wheel;
//
std::recursive_mutex
population_modification_mutex,
breed_mutex,
evaluate_fitness_mutex,
copy_chromosomes_mutex
;
//
void InitRandomGenerator();
void InitRouletteWheel();
void InitBreeder();
//
void EnsureSortedPopulation();
//
void BreedNewPopulation(std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>> population_new, int size);
void BreedNewPopulation_Thread(std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>> population_new, int size);
//
int DetermineEliteCount();
void SeedPopulationWithElites(std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>> population_new);
//
void EvaluateFitness_Thread(
std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>> _chromosomes,
std::function<double(std::shared_ptr<Chromosome>)> evaluation_callback
);
void EvaluateError_Thread(
std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>> _chromosomes,
std::function<double(std::shared_ptr<Chromosome>)> evaluation_callback
);
//
void CopyChromosomes(
std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>> _chromosomes_source,
std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>> _chromosomes_destination
);
void CopyChromosomes_Thread(
std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>> _chromosomes_source,
std::shared_ptr<std::vector<std::shared_ptr<Chromosome>>> _chromosomes_destination
);
//
std::shared_ptr<Chromosome> BreedChild();
//
int GetThreadCountSuggestion();
};
};

86
Random.cpp Normal file
View File

@ -0,0 +1,86 @@
//
#include "BitEvolver/Random.h"
//
#include <random>
#include <chrono>
//
namespace BitEvolver
{
//
Random::Random()
{
//
this->InitializeGenerators();
}
//
int Random::GetInt(int min, int max)
{
//
std::uniform_int_distribution<int> distribution(min, max);
//
int the_int = distribution(this->generator_mersenne_twister);
return the_int;
}
//
double Random::GetDouble(double min, double max)
{
//
std::uniform_real_distribution<double> distribution(min, max);
//
double the_double = distribution(this->generator_mersenne_twister);
return the_double;
}
//
double Random::GetNormal(double mean, double standard_deviation)
{
//
std::normal_distribution<double> distribution(mean, standard_deviation);
double d = distribution(this->generator_mersenne_twister);
return d;
}
//
bool Random::RollBool(double chance)
{
//
double d = this->GetDouble(0, 1);
if ( d <= chance ) {
return true;
}
return false;
}
//
void Random::InitializeGenerators()
{
// Mostly taken from
// http://www.cplusplus.com/reference/random/mersenne_twister_engine/seed/
typedef std::chrono::high_resolution_clock myclock;
//
myclock::time_point beginning = myclock::now();
myclock::duration d = myclock::now() - beginning;
unsigned seed = d.count();
// Seed our internal generator
this->generator_mersenne_twister.seed(seed);
}
};

41
Random.h Normal file
View File

@ -0,0 +1,41 @@
//
#pragma once
//
#include <random>
//
namespace BitEvolver
{
//
class Random
{
//
public:
//
Random();
//
int GetInt(int min, int max);
double GetDouble(double min, double max);
double GetNormal(double mean, double standard_deviation);
//
bool RollBool(double chance);
//
private:
//
std::mt19937_64 generator_mersenne_twister;
//
void InitializeGenerators();
};
};

271
RouletteWheel.cpp Normal file
View File

@ -0,0 +1,271 @@
//
#include "BitEvolver/Random.h"
#include "BitEvolver/RouletteWheel.h"
#include "BitEvolver/Chromosome.h"
//
#include <vector>
#include <mutex>
#include <algorithm>
#include <memory>
#include <iostream>
//
namespace BitEvolver
{
//
using std::cout;
using std::endl;
//
RouletteWheel::RouletteWheel()
{
//
this->Instantiate();
//
this->Reset();
}
//
void RouletteWheel::Reset()
{
//
this->ClearChromosomes();
}
//
void RouletteWheel::ClearChromosomes()
{
//
std::unique_lock<std::recursive_mutex> lock(this->chromosomes_mutex);
//
this->chromosomes.clear();
this->ChromosomesChanged();
}
//
void RouletteWheel::SetChromosomes(std::vector<std::shared_ptr<Chromosome>> _chromosomes)
{
//
std::unique_lock<std::recursive_mutex> lock(this->chromosomes_mutex);
//
this->ClearChromosomes();
//
this->chromosomes = _chromosomes;
//
this->ChromosomesChanged();
}
//
void RouletteWheel::AddChromosome(std::shared_ptr<Chromosome> _chromosome)
{
//
std::unique_lock<std::recursive_mutex> lock(this->chromosomes_mutex);
//
this->chromosomes.push_back(_chromosome);
this->ChromosomesChanged();
}
//
void RouletteWheel::AddChromosomes(std::vector<std::shared_ptr<Chromosome>> _chromosomes)
{
//
std::unique_lock<std::recursive_mutex> lock(this->chromosomes_mutex);
//
for ( std::shared_ptr<Chromosome> _chromosome : _chromosomes ) {
//
this->chromosomes.push_back(_chromosome);
}
//
this->ChromosomesChanged();
}
//
std::shared_ptr<Chromosome> RouletteWheel::Spin()
{
//
std::unique_lock<std::recursive_mutex> lock(this->chromosomes_mutex);
double
range_min, range_max,
spin
;
size_t i;
//
this->PopulateSlots();
//
if ( !this->wheel_slots.size() ) {
return nullptr;
}
// Spin a random number in our range
range_min = this->wheel_slots[0].first;
range_max = this->wheel_slots[this->wheel_slots.size()-1].first;
spin = this->random->GetDouble(range_min, range_max);
// Find the corresponding chromosome
for ( i=0; i<this->wheel_slots.size(); i++ ) {
if ( this->wheel_slots[i].first >= spin ) {
return this->wheel_slots[i].second;
}
}
// By default, return the first one I guess
return this->wheel_slots[0].second;
}
//
void RouletteWheel::Instantiate()
{
//
this->random = std::shared_ptr<Random>(
new Random()
);
}
//
std::vector<std::pair<double, std::shared_ptr<Chromosome>>> RouletteWheel::GetNormalizedChromosomeFitness()
{
//
std::unique_lock<std::recursive_mutex> lock(this->chromosomes_mutex);
std::vector< std::pair< double, std::shared_ptr<Chromosome> > > pairs;
std::pair< double, std::shared_ptr<Chromosome> > pair;
double
fitness, fitness_low, fitness_high
;
//
if ( !this->chromosomes.size() ) {
return pairs;
}
// Locate lowest and highest fitness
fitness_low = fitness_high = this->chromosomes[0]->GetFitness();
for ( std::shared_ptr<Chromosome> chromosome : this->chromosomes ) {
//
fitness = chromosome->GetFitness();
if ( fitness > fitness_high ) {
fitness_high = fitness;
}
if ( fitness < fitness_low ) {
fitness_low = fitness;
}
}
// Generate normalized pairs
for ( std::shared_ptr<Chromosome> chromosome : this->chromosomes ) {
//
pair.first = chromosome->GetFitness() - fitness_low;
pair.first *= pair.first; // Square to enhance the difference a little
pair.second = chromosome;
//
pairs.push_back(pair);
//
//cout << "[" << pair.first << "]" << chromosome->ToString() << endl;
}
return pairs;
}
//
void RouletteWheel::SortChromosomes()
{
//
std::unique_lock<std::recursive_mutex> lock(this->chromosomes_mutex);
//
if ( !this->chromosomes_need_sorting ) {
return;
}
//
std::sort(
this->chromosomes.begin(),
this->chromosomes.end(),
[]( const std::shared_ptr<Chromosome>& left, const std::shared_ptr<Chromosome>& right ) -> bool
{
//
if ( left->GetFitness() > right->GetFitness() ) {
return true;
}
return false;
}
);
//
this->chromosomes_need_sorting = false;
}
//
void RouletteWheel::PopulateSlots()
{
//
std::unique_lock<std::recursive_mutex> lock(this->chromosomes_mutex);
std::vector<std::pair<double, std::shared_ptr<Chromosome>>> chromosomes_normalized_fitness;
std::pair<double,std::shared_ptr<Chromosome>> wheel_slot;
double
slot_begin_value
;
//
if ( !this->slots_need_population ) {
return;
}
//
this->SortChromosomes();
//
this->wheel_slots.clear();
//
slot_begin_value = 0;
chromosomes_normalized_fitness = this->GetNormalizedChromosomeFitness();
for ( std::pair<double, std::shared_ptr<Chromosome>> pair: chromosomes_normalized_fitness ) {
//
wheel_slot.first = pair.first + slot_begin_value;
wheel_slot.second = pair.second;
//
this->wheel_slots.push_back(wheel_slot);
//
slot_begin_value += pair.first;
}
//
this->slots_need_population = false;
}
//
void RouletteWheel::ChromosomesChanged()
{
//
this->chromosomes_need_sorting = true;
this->slots_need_population = true;
}
};

68
RouletteWheel.h Normal file
View File

@ -0,0 +1,68 @@
//
#pragma once
//
#include <vector>
#include <mutex>
#include <memory>
//
namespace BitEvolver
{
//
class RouletteWheel
{
//
public:
//
RouletteWheel();
//
void Reset();
//
void ClearChromosomes();
void SetChromosomes(std::vector<std::shared_ptr<class Chromosome>> _chromosomes);
void AddChromosome(std::shared_ptr<class Chromosome> _chromosome);
void AddChromosomes(std::vector<std::shared_ptr<class Chromosome>> _chromosomes);
//
std::shared_ptr<Chromosome> Spin();
//
private:
//
std::shared_ptr<class Random> random;
//
std::vector<std::shared_ptr<class Chromosome>> chromosomes;
std::recursive_mutex chromosomes_mutex;
bool chromosomes_need_sorting;
//
std::vector<std::pair<double,std::shared_ptr<class Chromosome>>> wheel_slots;
bool slots_need_population;
//
void Instantiate();
//
std::vector<std::pair<double, std::shared_ptr<class Chromosome>>> GetNormalizedChromosomeFitness();
void SortChromosomes();
void PopulateSlots();
//
void ChromosomesChanged();
};
};