diff --git a/Breeder.cpp b/Breeder.cpp new file mode 100644 index 0000000..1a35347 --- /dev/null +++ b/Breeder.cpp @@ -0,0 +1,210 @@ + + +// +#include "BitEvolver/Random.h" +#include "BitEvolver/Chromosome.h" +#include "BitEvolver/Breeder.h" + + +// +#include + + +// +namespace BitEvolver +{ + // + using std::cout; + using std::endl; + + // + Breeder::Breeder(std::shared_ptr _random) + { + // + this->random = _random; + } + + // + std::shared_ptr Breeder::Breed( + std::shared_ptr mama, + std::shared_ptr 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 + 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(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, double mutation_rate) + { + // + int + i, + size + ; + + // + size = chromosome->GetBitCount(); + for ( i=0; irandom->RollBool(mutation_rate) ) { + chromosome->FlipBit(i); + } + } + + // + chromosome->ResetFitness(); + } + + // + int Breeder::PickRandomCrossoverPoint( + std::shared_ptr 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 kiddo, + std::shared_ptr 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; iSetBit( i, parent->GetBit(i) ); + } + + // + kiddo->ResetFitness(); + } +}; + + + + + + + diff --git a/Breeder.h b/Breeder.h new file mode 100644 index 0000000..0b6b4fb --- /dev/null +++ b/Breeder.h @@ -0,0 +1,57 @@ + + +// +#pragma once + + +// +namespace BitEvolver +{ + // + class Breeder + { + // + public: + + // + Breeder(std::shared_ptr _random); + + // + std::shared_ptr Breed( + std::shared_ptr mama, + std::shared_ptr 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 chromosome, double mutation_rate); + + // + private: + + // + std::shared_ptr random; + + // + int PickRandomCrossoverPoint( + std::shared_ptr chromosome, + Enums::CrossoverBounds crossover_bounds, + double crossover_point, + double crossover_point_std + ); + void ApplyCrossover( + std::shared_ptr kiddo, + std::shared_ptr parent, + Enums::CrossoverType crossover_type, + Enums::CrossoverBounds crossover_bounds, + double crossover_point, + double crossover_point_std + ); + }; +}; + diff --git a/Chromosome.cpp b/Chromosome.cpp new file mode 100644 index 0000000..c6b7761 --- /dev/null +++ b/Chromosome.cpp @@ -0,0 +1,228 @@ + + +// +#include "BitEvolver/Chromosome.h" +#include "BitEvolver/Random.h" + + +// +#include +#include +#include +#include +#include + + +// +namespace BitEvolver +{ + // + using std::string; + using std::to_string; + using std::stringstream; + + // + Chromosome::Chromosome(std::shared_ptr _random, int _bits) + { + // + this->random = _random; + this->SetBitCount(_bits); + + // + this->Reset(); + } + + // + void Chromosome::Reset() + { + // + this->Randomize(); + } + + // + void Chromosome::Randomize() + { + // + std::unique_lock lock(this->modification_mutex); + int i; + + // + this->bits.clear(); + + // + for ( i=0; ibits_count_desired; i++ ) { + this->bits.push_back(this->random->GetInt(0, 1)); + } + } + + // + void Chromosome::SetBitCount(int count) + { + // + std::unique_lock 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 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 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 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 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 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; + } +}; + + + diff --git a/Chromosome.h b/Chromosome.h new file mode 100644 index 0000000..b07eb37 --- /dev/null +++ b/Chromosome.h @@ -0,0 +1,83 @@ + + +// +#pragma once + + +// +#include "BitEvolver/Includes.h" + + +// +#include +#include +#include +#include +#include + + +// +namespace BitEvolver +{ + // + class Chromosome + { + // + public: + + // + Chromosome(std::shared_ptr _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 random; + + // + std::vector bits; + int bits_count_desired; + + // Fitness + double fitness; + + // Mutexes + std::recursive_mutex modification_mutex; + }; +}; + diff --git a/Defines.h b/Defines.h new file mode 100644 index 0000000..bbfde70 --- /dev/null +++ b/Defines.h @@ -0,0 +1,9 @@ +#ifndef BITEVOLVER_DEFINES_H +#define BITEVOLVER_DEFINES_H + + +// Nada for now + + + +#endif \ No newline at end of file diff --git a/Enums.h b/Enums.h new file mode 100644 index 0000000..bf3410c --- /dev/null +++ b/Enums.h @@ -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 \ No newline at end of file diff --git a/ForwardDeclarations.h b/ForwardDeclarations.h new file mode 100644 index 0000000..25f710e --- /dev/null +++ b/ForwardDeclarations.h @@ -0,0 +1,14 @@ +#ifndef BITEVOLVER_FORWARD_DECLARATIONS_H +#define BITEVOLVER_FORWARD_DECLARATIONS_H + +// +namespace BitEvolver +{ + // + class Population; + class Breeder; + class Chromosome; +}; + + +#endif \ No newline at end of file diff --git a/Includes.h b/Includes.h new file mode 100644 index 0000000..da3cbb8 --- /dev/null +++ b/Includes.h @@ -0,0 +1,13 @@ +#ifndef BITEVOLVER_INCLUDES_H +#define BITEVOLVER_INCLUDES_H + + +// +#include "BitEvolver/Defines.h" +#include "BitEvolver/Enums.h" + + + + + +#endif \ No newline at end of file diff --git a/Makefile b/Makefile index 792d600..2c6c801 100644 --- a/Makefile +++ b/Makefile @@ -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 $@) + + + + + + + diff --git a/Population.cpp b/Population.cpp new file mode 100644 index 0000000..85d82b7 --- /dev/null +++ b/Population.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + + +// +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; + int i; + + // + this->ClearPopulation(); + for ( i=0; ipopulation_size; i++ ) { + + // + chromosome = std::shared_ptr( + new Chromosome( this->random, _bit_length ) + ); + this->chromosomes.push_back(chromosome); + } + } + + // + void Population::PopulationChanged() + { + // + this->population_needs_sorting = true; + } + + // + std::vector> Population::GetChromosomes() + { + return this->chromosomes; + } + + // + void Population::GetChromosomes(std::shared_ptr>> _chromosomes) + { + // + _chromosomes->clear(); + for ( std::shared_ptr chromosome : this->chromosomes) { + _chromosomes->push_back(chromosome); + } + } + + // + std::shared_ptr 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> _chromosomes) + { + // + double fitness_sum; + double fitness_average; + + // + fitness_sum = 0; + for ( std::shared_ptr 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)> evaluation_callback) + { + // + std::shared_ptr>> _chromosomes_copy; + std::vector> threads; + std::shared_ptr thread; + int + threads_count, + i + ; + + // Make a new vector containing all current chromosomes + this->population_modification_mutex.lock(); + _chromosomes_copy = std::shared_ptr>>( + new std::vector>() + ); + 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( + new std::thread(&Population::EvaluateFitness_Thread, this, _chromosomes_copy, evaluation_callback) + ); + threads.push_back(thread); + } + + // Wait for threads to finish + for ( i=0; ijoin(); + } + } + + // + void Population::EvaluateError(std::function)> evaluation_callback) + { + // + std::shared_ptr>> _chromosomes_copy; + std::vector> threads; + std::shared_ptr thread; + int + threads_count, + i + ; + + // Make a new vector containing all current chromosomes + this->population_modification_mutex.lock(); + _chromosomes_copy = std::shared_ptr>>( + new std::vector>() + ); + 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( + new std::thread(&Population::EvaluateError_Thread, this, _chromosomes_copy, evaluation_callback) + ); + threads.push_back(thread); + } + + // Wait for threads to finish + for ( i=0; ijoin(); + } + } + + // + void Population::Evolve() + { + // + std::shared_ptr > > population_new; + + // + if ( this->chromosomes.size() == 0 ) { + return; + } + + // + this->EnsureSortedPopulation(); + + // + population_new = std::shared_ptr< + std::vector< + std::shared_ptr + > + >( + new std::vector>() + ); + + // 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> _chromosomes) + { + // + for ( std::shared_ptr chromosome : chromosomes ) { + cout << chromosome->ToString() << endl; + } + cout << "Average Fitness --> " << this->GetAverageFitness(_chromosomes) << endl; + } + + // + void Population::InitRandomGenerator() + { + // + this->random = std::shared_ptr( + new Random() + ); + } + + // + void Population::InitRouletteWheel() + { + // + this->roulette_wheel = std::shared_ptr( + new RouletteWheel() + ); + } + + // + void Population::InitBreeder() + { + // + if ( !this->random ) { + throw std::runtime_error("Population::InitBreeder() - Should come after InitRandomGenerator()"); + } + + // + this->breeder = std::shared_ptr( + 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& left, std::shared_ptr& right ) -> bool + { + // + if ( left->GetFitness() > right->GetFitness() ) { + return true; + } + + return false; + } + ); + + // + this->population_needs_sorting = false; + } + + // + void Population::BreedNewPopulation(std::shared_ptr>> population_new, int size) + { + // + std::vector> threads; + std::shared_ptr 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( + 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>> population_new, int size) + { + // + std::shared_ptr 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>> population_new) + { + // + std::unique_lock lock(this->population_modification_mutex); + std::shared_ptr>> elites; + std::vector> threads; + std::shared_ptr thread; + int + elites_count, + i + ; + + // Determine how many elites to copy + elites_count = this->DetermineEliteCount(); + elites = std::shared_ptr>>( + new std::vector>() + ); + + // First, copy over just the pointers + for ( i=0; ichromosomes.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>> _chromosomes, + std::function)> evaluation_callback + ) + { + // + std::shared_ptr 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>> _chromosomes, + std::function)> evaluation_callback + ) + { + // + std::shared_ptr 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>> _chromosomes_source, + std::shared_ptr>> _chromosomes_destination + ) + { + // + std::vector> threads; + std::shared_ptr thread; + int + threads_count, + i + ; + + // Spawn threads + threads_count = this->GetThreadCountSuggestion(); + for ( i=0; i( + new std::thread(&Population::CopyChromosomes_Thread, this, _chromosomes_source, _chromosomes_destination) + ); + threads.push_back(thread); + } + + // Wait for threads to finish + for ( i=0; ijoin(); + } + } + + // + void Population::CopyChromosomes_Thread( + std::shared_ptr>> _chromosomes_source, + std::shared_ptr>> _chromosomes_destination + ) + { + // + std::shared_ptr + 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( + 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 Population::BreedChild() + { + // + std::shared_ptr + 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; + } +}; + + + + + + + + + diff --git a/Population.h b/Population.h new file mode 100644 index 0000000..e6ab36b --- /dev/null +++ b/Population.h @@ -0,0 +1,178 @@ + + +// +#pragma once + + +// +#include "BitEvolver/Includes.h" + + +// +#include +#include +#include +#include + + +// +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> GetChromosomes(); + void GetChromosomes(std::shared_ptr>> _chromosomes); + std::shared_ptr GetChampion(); + + // + double GetAverageFitness(); + double GetAverageFitness(std::vector> _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)> evaluation_callback); + void EvaluateError(std::function)> evaluation_callback); + + // + void Evolve(); + int GetEvolutionNumber(); + + // + void PrintPopulation(); + void PrintPopulation(std::vector> _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 random; + std::shared_ptr breeder; + + // + std::vector> 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 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>> population_new, int size); + void BreedNewPopulation_Thread(std::shared_ptr>> population_new, int size); + + // + int DetermineEliteCount(); + void SeedPopulationWithElites(std::shared_ptr>> population_new); + + // + void EvaluateFitness_Thread( + std::shared_ptr>> _chromosomes, + std::function)> evaluation_callback + ); + void EvaluateError_Thread( + std::shared_ptr>> _chromosomes, + std::function)> evaluation_callback + ); + + // + void CopyChromosomes( + std::shared_ptr>> _chromosomes_source, + std::shared_ptr>> _chromosomes_destination + ); + void CopyChromosomes_Thread( + std::shared_ptr>> _chromosomes_source, + std::shared_ptr>> _chromosomes_destination + ); + + // + std::shared_ptr BreedChild(); + + // + int GetThreadCountSuggestion(); + }; +}; + + + diff --git a/Random.cpp b/Random.cpp new file mode 100644 index 0000000..87726a5 --- /dev/null +++ b/Random.cpp @@ -0,0 +1,86 @@ + + +// +#include "BitEvolver/Random.h" + + +// +#include +#include + + +// +namespace BitEvolver +{ + // + Random::Random() + { + // + this->InitializeGenerators(); + } + + // + int Random::GetInt(int min, int max) + { + // + std::uniform_int_distribution 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 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 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); + } +}; + + diff --git a/Random.h b/Random.h new file mode 100644 index 0000000..9d9d8d4 --- /dev/null +++ b/Random.h @@ -0,0 +1,41 @@ + + +// +#pragma once + + +// +#include + + +// +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(); + + }; +}; diff --git a/RouletteWheel.cpp b/RouletteWheel.cpp new file mode 100644 index 0000000..cd18191 --- /dev/null +++ b/RouletteWheel.cpp @@ -0,0 +1,271 @@ + + + +// +#include "BitEvolver/Random.h" +#include "BitEvolver/RouletteWheel.h" +#include "BitEvolver/Chromosome.h" + + +// +#include +#include +#include +#include +#include + + +// +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 lock(this->chromosomes_mutex); + + // + this->chromosomes.clear(); + this->ChromosomesChanged(); + } + + // + void RouletteWheel::SetChromosomes(std::vector> _chromosomes) + { + // + std::unique_lock lock(this->chromosomes_mutex); + + // + this->ClearChromosomes(); + + // + this->chromosomes = _chromosomes; + + // + this->ChromosomesChanged(); + } + + // + void RouletteWheel::AddChromosome(std::shared_ptr _chromosome) + { + // + std::unique_lock lock(this->chromosomes_mutex); + + // + this->chromosomes.push_back(_chromosome); + this->ChromosomesChanged(); + } + + // + void RouletteWheel::AddChromosomes(std::vector> _chromosomes) + { + // + std::unique_lock lock(this->chromosomes_mutex); + + // + for ( std::shared_ptr _chromosome : _chromosomes ) { + + // + this->chromosomes.push_back(_chromosome); + } + + // + this->ChromosomesChanged(); + } + + // + std::shared_ptr RouletteWheel::Spin() + { + // + std::unique_lock 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; iwheel_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( + new Random() + ); + } + + // + std::vector>> RouletteWheel::GetNormalizedChromosomeFitness() + { + // + std::unique_lock lock(this->chromosomes_mutex); + std::vector< std::pair< double, std::shared_ptr > > pairs; + std::pair< double, std::shared_ptr > 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 : 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 : 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 lock(this->chromosomes_mutex); + + // + if ( !this->chromosomes_need_sorting ) { + return; + } + + // + std::sort( + this->chromosomes.begin(), + this->chromosomes.end(), + []( const std::shared_ptr& left, const std::shared_ptr& right ) -> bool + { + // + if ( left->GetFitness() > right->GetFitness() ) { + return true; + } + + return false; + } + ); + + // + this->chromosomes_need_sorting = false; + } + + // + void RouletteWheel::PopulateSlots() + { + // + std::unique_lock lock(this->chromosomes_mutex); + std::vector>> chromosomes_normalized_fitness; + std::pair> 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> 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; + } +}; + + + diff --git a/RouletteWheel.h b/RouletteWheel.h new file mode 100644 index 0000000..b31baaf --- /dev/null +++ b/RouletteWheel.h @@ -0,0 +1,68 @@ + + + +// +#pragma once + + +// +#include +#include +#include + + +// +namespace BitEvolver +{ + // + class RouletteWheel + { + // + public: + + // + RouletteWheel(); + + // + void Reset(); + + // + void ClearChromosomes(); + void SetChromosomes(std::vector> _chromosomes); + void AddChromosome(std::shared_ptr _chromosome); + void AddChromosomes(std::vector> _chromosomes); + + // + std::shared_ptr Spin(); + + // + private: + + // + std::shared_ptr random; + + // + std::vector> chromosomes; + std::recursive_mutex chromosomes_mutex; + bool chromosomes_need_sorting; + // + std::vector>> wheel_slots; + bool slots_need_population; + + + // + void Instantiate(); + + // + std::vector>> GetNormalizedChromosomeFitness(); + void SortChromosomes(); + void PopulateSlots(); + + // + void ChromosomesChanged(); + + }; +}; + + +