First commit - Seems to pass "all 1's" evolution test

This commit is contained in:
Mike 2018-04-13 23:59:20 -07:00
parent 1ee1d3282b
commit 854ad5a1a3
13 changed files with 1231 additions and 0 deletions

152
Breeder.cpp Normal file
View File

@ -0,0 +1,152 @@
//
#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,
double crossover_rate,
double mutation_rate
)
{
//
std::shared_ptr<Chromosome> 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<Chromosome>(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> 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; i<size; i++ ) {
//
if ( this->random->RollBool(mutation_rate) ) {
chromosome->FlipBit(i);
}
}
//
chromosome->ResetFitness();
}
//
int Breeder::PickRandomCrossoverPoint(std::shared_ptr<Chromosome> 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<Chromosome> kiddo, std::shared_ptr<Chromosome> 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; i<bits_count; i++) {
kiddo->SetBit( i, parent->GetBit(i) );
}
//
kiddo->ResetFitness();
}
};

41
Breeder.h Normal file
View File

@ -0,0 +1,41 @@
//
#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,
double crossover_rate,
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, double crossover_rate);
void ApplyCrossover(std::shared_ptr<class Chromosome> kiddo, std::shared_ptr<class Chromosome> parent, double crossover_rate);
};
};

184
Chromosome.cpp Normal file
View File

@ -0,0 +1,184 @@
//
#include "BitEvolver/Chromosome.h"
#include "BitEvolver/Random.h"
//
#include <memory>
#include <string>
#include <sstream>
#include <vector>
//
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()
{
//
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)
{
//
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;
}
};

71
Chromosome.h Normal file
View File

@ -0,0 +1,71 @@
//
#pragma once
//
#include "BitEvolver/Includes.h"
//
#include <memory>
#include <string>
#include <sstream>
#include <vector>
//
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();
//
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;
};
};

13
Defines.h Normal file
View File

@ -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

9
Enums.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef BITEVOLVER_ENUMS_H
#define BITEVOLVER_ENUMS_H
//
#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

122
Makefile
View File

@ -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 $@)

389
Population.cpp Normal file
View File

@ -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 <memory>
#include <vector>
#include <mutex>
#include <iostream>
#include <algorithm>
#include <thread>
//
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> 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::SetMutationRate(double r)
{
//
this->mutation_rate = r;
}
//
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>>()
);
// 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<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::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
;
//
thread_count = std::thread::hardware_concurrency();
//
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();
}
}
//
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();
}
}
//
std::shared_ptr<Chromosome> Population::BreedChild()
{
//
std::shared_ptr<Chromosome>
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<Chromosome> 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];
}
};

96
Population.h Normal file
View File

@ -0,0 +1,96 @@
//
#pragma once
//
#include "BitEvolver/Includes.h"
//
#include <memory>
#include <vector>
#include <mutex>
//
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 SetMutationRate(double r);
//
void Evolve();
int GetEvolutionNumber();
//
void PrintPopulation();
void PrintPopulation(std::vector<std::shared_ptr<class Chromosome>> _chromosomes);
//
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;
double mutation_rate;
int evolution_number;
//
std::mutex breed_mutex;
//
void InitRandomGenerator();
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);
//
std::shared_ptr<Chromosome> BreedChild();
std::shared_ptr<Chromosome> PickChromosomeForBreeding();
};
};

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();
};
};