/include/genalg.h (c7d952e63380afac3639c9d951ceb7c5a3470eda) (7164 bytes) (mode 100755) (type blob)

#ifndef GENALG_H
#define GENALG_H

#include <climits>
#include <cmath>
//#include <cstdlib> // for rand()
#include <cstring>

#include <bitset>
#include <random>
#include <vector>

namespace genalg {

template<typename T>
struct Phenotype {
	double fitness;
	T chromosome;
};

//typedef double (*FitnessFunc)(Phenotype* p);

template<typename T, typename F, typename U, typename R>
std::vector<Phenotype<T>> Iterate(
		std::vector<Phenotype<T>>* population,
		const F &FitnessFunc,
		U user_data,
		R* rng,
		double crossover_rate,
		double mutation_rate );

template<typename T, typename R>
std::vector<Phenotype<T>> RandomGarbage(
		size_t members,
		R* rng );

#ifdef GENALG_IMPLEMENTATION

/** \brief Breeds a new generation of phenotypes.
 *
 * This is the main evolution function. Feed it your population and get
 * a slightly evolved population out the other end. Do this many times
 * to optimize for the fitness function.
 *
 * The new population's size will be a multiple of 2 due to how the
 * recombination works. Aside from that, the input and output
 * populations will always be the same size.
 *
 * When experimenting with crossover rates and mutation rates, a value
 * of 0.7 has been suggested for the crossover and 0.01 or 0.001 for the
 * mutation.
 *
 * Note that the human generational mutation rate is roughly on the
 * order of 0.00000002, or 1 out of every 500 million base pairs. For
 * most contemporary programs, this would translate to negligibly few
 * mutations.
 *
 * Example:
 * `population = genalg::Iterate( &population, MyFunc, my_data, &mt, 0.7, 0.01 );`
 *
 * @param  population     Pointer to `std::vector` of phenotypes.
 * @param  FitnessFunc    Function that ingests a
 *                        `Phenotype<MyChromosome>*` and user data and
 *                        returns a floating-point fitness value.
 * @param  user_data      A variable of any type to be passed as the
 *                        second parameter to `FitnessFunc`.
 * @param  rng            Pointer to instance of an RNG from `<random>`.
 * @param  crossover_rate The probability that 2 chosen chromosomes
 *                        will swap bits.
 * @param  mutation_rate  A bit's probability of flipping.
 * @return `std::vector` of the new generation of phenotypes.
 */
template<typename T, typename F, typename U, typename R>
std::vector<Phenotype<T>> Iterate(
		std::vector<Phenotype<T>>* population,
		const F &FitnessFunc,
		U user_data,
		R* rng,
		double crossover_rate,
		double mutation_rate ){

	const size_t chromosome_bytes = sizeof(T);
	const size_t chromosome_bits = chromosome_bytes * CHAR_BIT;
	// TODO(fluffrabbit): Investigate the contiguousness of std::bitset.
	std::bitset<chromosome_bits> bits[3];

	double total_fitness = 0.0;

	// Decide the fitness of each member of the population.
	for( auto &p : *population ){
		p.fitness = FitnessFunc( &p, user_data );
		// Yes, negative fitness is possible.
		total_fitness += std::max( 0.0, static_cast<double>( p.fitness ) );
	}

	// If the total fitness is infinity or NaN, just try not to cause a crash.
	if( std::isinf( total_fitness ) || std::isnan( total_fitness ) ){
		total_fitness = 0.0;
	}

	// Initialize the random number distributions.
	std::uniform_real_distribution<double> roulette_dist( 0.0, total_fitness );
	std::uniform_real_distribution<double> unit_dist( 0.0, 1.0 );
	std::uniform_int_distribution<size_t> gene_dist( 0, chromosome_bits );

	std::vector<Phenotype<T>> new_population;

	// Iterate through the population, producing 2 new members for every 2 old members.
	for( size_t half_n = 0; half_n < population->size() / 2; half_n++ ){
		double
			slot = 0.0,
			ball1 = roulette_dist( *rng ),
			ball2 = roulette_dist( *rng );
		bool
			got_bits1 = false,
			got_bits2 = false;

		// Pick 2 mates using roulette wheel selection.
		for( auto &p : *population ){
			int target = 0;
			if( ball1 >= slot && ball1 < slot + p.fitness ){
				target = 1;
				got_bits1 = true;
			}
			if( ball2 >= slot && ball2 < slot + p.fitness ){
				target = 2;
				got_bits2 = true;
			}
			if( target > 0 ){
				std::memcpy(
					static_cast<void*>( target == 1 ? &bits[1] : &bits[2] ),
					static_cast<void*>( &p.chromosome ),
					chromosome_bytes
				);
			}
			if( got_bits1 && got_bits2 ){
				break;
			}
			slot += p.fitness;
		}

		if( unit_dist( *rng ) < crossover_rate ){
			// Swap genes.
			size_t gene_offset_bits = gene_dist( *rng );
			size_t gene_offset_bytes = gene_offset_bits / CHAR_BIT;

			// Use bits[0] as a buffer for the first chromosome's data.
			std::memcpy(
				static_cast<void*>( &bits[0] ),
				static_cast<void*>( &bits[1] ),
				chromosome_bytes
			);

			if( gene_offset_bytes > 0 ){
				// Swap bytes from the starts of the chromosomes to the offset.
				// This is faster than iterating over every bit.
				std::memcpy(
					static_cast<void*>( &bits[1] ),
					static_cast<void*>( &bits[2] ),
					gene_offset_bytes
				);
				std::memcpy(
					static_cast<void*>( &bits[2] ),
					static_cast<void*>( &bits[0] ),
					gene_offset_bytes
				);
			}

			// Swap the bits in the byte of the crossover.
			for( size_t i = gene_offset_bytes * CHAR_BIT; i < gene_offset_bits; i++ ){
				bits[1][i] = bits[2][i];
				bits[2][i] = bits[0][i];
			}
		}

		// Mutate the chromosomes.
		for( size_t i = 0; i < chromosome_bits; i++ ){
			if( unit_dist( *rng ) < mutation_rate ){
				bits[1].flip( i );
			}
			if( unit_dist( *rng ) < mutation_rate ){
				bits[2].flip( i );
			}
		}

		// Add the phenotypes to the new population.
		for( int i = 0; i < 2; i++ ){
			new_population.push_back( { 0.0, {} } );
			std::memcpy(
				static_cast<void*>( &new_population.back().chromosome ),
				static_cast<void*>( i == 0 ? &bits[1] : &bits[2] ),
				chromosome_bytes
			);
		}
	}

	return new_population;
}

/** \brief Returns a randomized `std::vector` of `Phenotype`s.
 *
 * Returns a vector containing `Phenotype`s with randomized chromosomes.
 * The randomization happens per byte without knowledge of what the
 * chromosome affects. This is a worst-case initialization for a quick
 * start that <b>should not be used in production</b>.
 *
 * Example:
 * `auto population = genalg::RandomGarbage<MyChromosome>( 20, &mt );`
 *
 * You should initialize with plausible random values instead.
 *
 * @param  <T>     struct type to use for chromosomes.
 * @param  members Number of `Phenotype`s to create.
 * @param  rng     Pointer to an instance of an RNG from `<random>`.
 * @return `std::vector` of randomized `Phenotype`s.
 */
template<typename T, typename R>
std::vector<Phenotype<T>> RandomGarbage(
		size_t members,
		R* rng ){

	std::vector<Phenotype<T>> population;

	unsigned char bytes[sizeof(T)];

	std::uniform_int_distribution<int> byte_dist( 0, UCHAR_MAX );

	// Create a random initial population.
	for( size_t i = 0; i < members; i++ ){
		for( unsigned char &b : bytes ){
			//b = std::rand() % ( UCHAR_MAX + 1 );
			b = byte_dist( *rng );
		}
		population.push_back( {
			0.0,
			*static_cast<T*>( static_cast<void*>( bytes ) )
		} );
	}
	return population;
}

#endif // GENALG_IMPLEMENTATION

} // namespace genalg

#endif // GENALG_H


Mode Type Size Ref File
100644 blob 98 227abf3bfa53b2530dcc74495da7bd0ccdcb0775 .gitignore
100644 blob 225 9b00c2c2e7b4f0c1e338fdead65f17ba0af089c1 COPYING
100755 blob 43 45aea818a4a3202b2467509f28a481cce08834d2 Confectioner.command
100644 blob 14015 649b7f0c112c3ac13287bfe88b949fec50356e4d Makefile
100644 blob 2723 b5a3f573f076ef740ca742ec9598043732e10c0e README.md
040000 tree - 6b3a1677d07517c1f83769dd7675fe6bb9d7a269 base
100755 blob 156 84cb1387849f2ca98e53e43536d00af2dfabf7d3 caveconfec
100755 blob 28 41b0ef285892c86306eaa269f366dd04cb633d21 caveconfec.bat
100644 blob 198037 a0180394c9bf29c02b7ef05916bd5573e3f37da2 confec.cpp
100644 blob 487269 29cfd3578eb40b1f039e271bcaa81af49d1b7f3c gamecontrollerdb.txt
040000 tree - 62e9d686bbab52d3d88886390b437a3ecef315de include
100755 blob 12081 ad29f012941aedfd4ee7232ed95fb68c8c5244c9 index-template.html
100755 blob 1065 a460e3c74b8fa53a6f609944ef7e41558479e73f libs.cpp
100755 blob 27581 8350a63e947e8a4a55608fd090d128fef7b969a1 micropather.cpp
100644 blob 141235 f54e2d2631a628876a631456c043b77da5db78bd openjdk.pem
100755 blob 8 e9a74187b02a27b165dfa4f93bf6f060376d0ee6 steam_appid.txt
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/mse/ConfectionerEngine

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/mse/ConfectionerEngine

Clone this repository using git:
git clone git://git.rocketgit.com/user/mse/ConfectionerEngine

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main