diff --git a/Breeder.cpp b/Breeder.cpp new file mode 100644 index 0000000..f4d8fa0 --- /dev/null +++ b/Breeder.cpp @@ -0,0 +1,152 @@ + + +// +#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, + double crossover_rate, + double mutation_rate + ) + { + // + std::shared_ptr kiddo; + int bit_length; + + // For now, don't crossover unless the bit lengths are identical + bit_length = mama->GetBitCount(); + if ( papa->GetBitCount() != bit_length ) { + throw std::runtime_error("Breeder::Breed() - Incompatible parents"); + } + + // Directly copy the mama + kiddo = std::shared_ptr(new Chromosome(this->random, bit_length)); + *kiddo = *mama; + + // Apply crossover + this->ApplyCrossover(kiddo, papa, crossover_rate); + + // Apply mutation + this->Mutate(kiddo, mutation_rate); + + return kiddo; + } + + // + void Breeder::Mutate(std::shared_ptr chromosome, double mutation_rate) + { + // + int i, size; + + // + #warning "For efficiency, let's instead roll the number of bits to flip in advance, and loop that many times (faster than rolling for every bit)" + + // + size = chromosome->GetBitCount(); + for ( i=0; irandom->RollBool(mutation_rate) ) { + chromosome->FlipBit(i); + } + } + + // + chromosome->ResetFitness(); + } + + // + int Breeder::PickRandomCrossoverPoint(std::shared_ptr chromosome, double crossover_rate) + { + // + double crossover_point_double; + int crossover_point; + int bit_count; + + // + bit_count = chromosome->GetBitCount(); + + /** + Choose a double between [0.0,1.0] for the crossover point. + Use normal distribution, with the mean to the default point, + and 1.0 as the std. + That way, there is still randomness to the crossover point, + but it still generally sticks near the default + */ + crossover_point_double = this->random->GetNormal(crossover_rate, 1.0); + + // Apply to the actual int length + crossover_point = crossover_point_double * bit_count; + + // Loop around to keep in bounds + while ( crossover_point < 0 ) + { + crossover_point += bit_count; + } + while ( crossover_point >= bit_count) + { + crossover_point -= bit_count; + } + + return crossover_point; + } + + // + void Breeder::ApplyCrossover(std::shared_ptr kiddo, std::shared_ptr parent, double crossover_rate) + { + // + int + bits_count, + crossover_point, + i + ; + + // + 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 = this->PickRandomCrossoverPoint(kiddo, crossover_rate); + + // Begin copying the parent at the crossover point and beyond + // (not before) + for ( i=crossover_point; iSetBit( i, parent->GetBit(i) ); + } + + // + kiddo->ResetFitness(); + } +}; + + + + + + + diff --git a/Breeder.h b/Breeder.h new file mode 100644 index 0000000..7decf6e --- /dev/null +++ b/Breeder.h @@ -0,0 +1,41 @@ + + +// +#pragma once + + +// +namespace BitEvolver +{ + // + class Breeder + { + // + public: + + // + Breeder(std::shared_ptr _random); + + // + std::shared_ptr Breed( + std::shared_ptr mama, + std::shared_ptr papa, + double crossover_rate, + double mutation_rate + ); + + // + void Mutate(std::shared_ptr chromosome, double mutation_rate); + + // + private: + + // + std::shared_ptr random; + + // + int PickRandomCrossoverPoint(std::shared_ptr chromosome, double crossover_rate); + void ApplyCrossover(std::shared_ptr kiddo, std::shared_ptr parent, double crossover_rate); + }; +}; + diff --git a/Chromosome.cpp b/Chromosome.cpp new file mode 100644 index 0000000..0fde499 --- /dev/null +++ b/Chromosome.cpp @@ -0,0 +1,184 @@ + + +// +#include "BitEvolver/Chromosome.h" +#include "BitEvolver/Random.h" + + +// +#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() + { + // + 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) + { + // + this->bits_count_desired = count; + this->Randomize(); + } + + // + int Chromosome::GetBitCount() + { + return (int)this->bits.size(); + } + + // + void Chromosome::FlipBit(int index) + { + // + 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) + { + // + 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) + { + // + 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; + } + + // + string Chromosome::ToString() + { + // + stringstream s; + + // + for ( bool b : this->bits ) { + + // + if ( b ) { + s << "1"; + } + else{ + s << "0"; + } + } + + // + return s.str(); + } + + // + const Chromosome& Chromosome::operator=(const Chromosome& other) + { + // + 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..a403b68 --- /dev/null +++ b/Chromosome.h @@ -0,0 +1,71 @@ + + +// +#pragma once + + +// +#include "BitEvolver/Includes.h" + + +// +#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(); + + // + 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; + }; +}; + diff --git a/Defines.h b/Defines.h new file mode 100644 index 0000000..61a3ba4 --- /dev/null +++ b/Defines.h @@ -0,0 +1,13 @@ +#ifndef BITEVOLVER_DEFINES_H +#define BITEVOLVER_DEFINES_H + + + +// +#define BIT_EVOLVER_POPULATION_DEFAULT_POPULATION_SIZE 100 +#define BIT_EVOLVER_POPULATION_DEFAULT_MUTATE_RATE 0.01 +#define BIT_EVOLVER_POPULATION_DEFAULT_CROSSOVER 0.7 + + + +#endif \ No newline at end of file diff --git a/Enums.h b/Enums.h new file mode 100644 index 0000000..b108c2c --- /dev/null +++ b/Enums.h @@ -0,0 +1,9 @@ +#ifndef BITEVOLVER_ENUMS_H +#define BITEVOLVER_ENUMS_H + + +// + + + +#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..5b71764 100644 --- a/Makefile +++ b/Makefile @@ -1 +1,123 @@ + +# 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)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 $@) + + +# 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..1458be2 --- /dev/null +++ b/Population.cpp @@ -0,0 +1,389 @@ + + +// +#include "BitEvolver/Includes.h" +#include "BitEvolver/Random.h" +#include "BitEvolver/Population.h" +#include "BitEvolver/Breeder.h" +#include "BitEvolver/Chromosome.h" + + +// +#include +#include +#include +#include +#include +#include + + +// +namespace BitEvolver +{ + // + using std::cout; + using std::endl; + + // + Population::Population() + { + // + this->InitRandomGenerator(); + this->InitBreeder(); + + // + this->Reset(); + } + + // + void Population::Reset() + { + // + this->population_size = BIT_EVOLVER_POPULATION_DEFAULT_POPULATION_SIZE; + this->SetMutationRate(BIT_EVOLVER_POPULATION_DEFAULT_MUTATE_RATE); + this->evolution_number = 0; + + // + 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::SetMutationRate(double r) + { + // + this->mutation_rate = r; + } + + // + 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>() + ); + + // Start the new population off with our champion, + // so the best score always carries over (elitism = 1 unit) + #warning "Elitism is only 1 right now" + population_new->push_back(this->chromosomes[0]); + + // 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::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 + ; + + // + thread_count = std::thread::hardware_concurrency(); + + // + 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(); + } + } + + // + 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(); + } + } + + // + std::shared_ptr Population::BreedChild() + { + // + std::shared_ptr + mama, papa, kiddo + ; + + // Pick two parents + mama = this->PickChromosomeForBreeding(); + papa = this->PickChromosomeForBreeding(); + + // + kiddo = this->breeder->Breed(mama, papa, BIT_EVOLVER_POPULATION_DEFAULT_CROSSOVER, this->mutation_rate); + + return kiddo; + } + + /** + (1) We want to pick the best chromosomes to repopulate + (2) [not in lecture] We want to add a bit of randomness + to the selection process, such that the worst chromosomes + can still possibly be picked, and the best can still + possibly be not-picked; Their fitness only increases + the probability they're picked. + */ + std::shared_ptr Population::PickChromosomeForBreeding() + { + // + double normal; + int chromosome_index; + + // + this->EnsureSortedPopulation(); + + /** + Grab normal with 0 at the mean, and the standard deviation equal + to 1/2 of the population size. Then make that an absolute value. + This will make top/best chromosomes more likely to be picked, + with the far/low end being much less likely + */ + #warning "Need to upgrade this to Roulette Wheel" + // Repeat as needed, since the normal generator might actually + // give us an out-of-bounds result sometimes + while ( true ) + { + // + normal = this->random->GetNormal(0, 0.5); + chromosome_index = abs( normal * this->chromosomes.size() + ); + if ( chromosome_index >= 0 && chromosome_index < (int)this->chromosomes.size() ) { + break; + } + } + + // + return this->chromosomes[chromosome_index]; + } +}; + + + + + + + + + diff --git a/Population.h b/Population.h new file mode 100644 index 0000000..2e0c703 --- /dev/null +++ b/Population.h @@ -0,0 +1,96 @@ + + +// +#pragma once + + +// +#include "BitEvolver/Includes.h" + + +// +#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 SetMutationRate(double r); + + // + void Evolve(); + int GetEvolutionNumber(); + + // + void PrintPopulation(); + void PrintPopulation(std::vector> _chromosomes); + + // + private: + + // + std::shared_ptr random; + std::shared_ptr breeder; + + // + std::vector> chromosomes; + int population_size; + bool population_needs_sorting; + double mutation_rate; + int evolution_number; + + // + std::mutex breed_mutex; + + // + void InitRandomGenerator(); + void InitBreeder(); + + // + void EnsureSortedPopulation(); + + // + void BreedNewPopulation(std::shared_ptr>> population_new, int size); + void BreedNewPopulation_Thread(std::shared_ptr>> population_new, int size); + + // + std::shared_ptr BreedChild(); + std::shared_ptr PickChromosomeForBreeding(); + + }; +}; + + + 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(); + + }; +};