/include/ne.h (5e80231c27b73e547531f7b35b145be6bb6efaa8) (16479 bytes) (mode 100755) (type blob)

#ifndef NE_H
#define NE_H

#include <math.h>
#include <stdlib.h>

// From stretchy_buffer.h version 1.03 (https://github.com/nothings/stb)
#ifndef STB_STRETCHY_BUFFER_H_INCLUDED
#define STB_STRETCHY_BUFFER_H_INCLUDED

#ifndef NO_STRETCHY_BUFFER_SHORT_NAMES
#define sb_free   stb_sb_free
#define sb_push   stb_sb_push
#define sb_count  stb_sb_count
#define sb_add    stb_sb_add
#define sb_last   stb_sb_last
#endif

#define stb_sb_free(a)         ((a) ? free(stb__sbraw(a)),0 : 0)
#define stb_sb_push(a,v)       (stb__sbmaybegrow(a,1), (a)[stb__sbn(a)++] = (v))
#define stb_sb_count(a)        ((a) ? stb__sbn(a) : 0)
#define stb_sb_add(a,n)        (stb__sbmaybegrow(a,n), stb__sbn(a)+=(n), &(a)[stb__sbn(a)-(n)])
#define stb_sb_last(a)         ((a)[stb__sbn(a)-1])

#define stb__sbraw(a) ((int *) (a) - 2)
#define stb__sbm(a)   stb__sbraw(a)[0]
#define stb__sbn(a)   stb__sbraw(a)[1]

#define stb__sbneedgrow(a,n)  ((a)==0 || stb__sbn(a)+(n) >= stb__sbm(a))
#define stb__sbmaybegrow(a,n) (stb__sbneedgrow(a,(n)) ? stb__sbgrow(a,n) : 0)
#define stb__sbgrow(a,n)      (*((void **)&(a)) = stb__sbgrowf((a), (n), sizeof(*(a))))

static void * stb__sbgrowf(void *arr, int increment, int itemsize)
{
   int dbl_cur = arr ? 2*stb__sbm(arr) : 0;
   int min_needed = stb_sb_count(arr) + increment;
   int m = dbl_cur > min_needed ? dbl_cur : min_needed;
   int *p = (int *) realloc(arr ? stb__sbraw(arr) : 0, itemsize * m + sizeof(int)*2);
   if (p) {
      if (!arr)
         p[1] = 0;
      p[0] = m;
      return p+2;
   } else {
      #ifdef STRETCHY_BUFFER_OUT_OF_MEMORY
      STRETCHY_BUFFER_OUT_OF_MEMORY ;
      #endif
      return (void *) (2*sizeof(int)); // try to force a NULL pointer exception later
   }
}
#endif // STB_STRETCHY_BUFFER_H_INCLUDED

#ifndef NE_REAL
	#define NE_REAL float
#endif

typedef struct {
	NE_REAL gradient;
	NE_REAL bias;
	NE_REAL* weights;
} ne_Neuron;

typedef ne_Neuron* ne_Layer;

typedef struct {
	NE_REAL fitness;
	ne_Layer* layers;
} ne_Network;

typedef ne_Network* ne_Population;

double ne_RandomUnit();

double ne_RandomClamped();

ne_Population ne_RandomNetworks(
		int* topology,
		int members,
		unsigned int rand_seed );

void ne_CopyNetwork(
		ne_Network* src,
		ne_Network* dest );

void ne_FreeNetwork( ne_Network* net );

void ne_FreePopulation( ne_Population* population );

void ne_Iterate(
		ne_Population* population,
		unsigned int rand_seed,
		double crossover_rate,
		double mutation_rate,
		int mutate_gradients );

void ne_GetOutputs(
		ne_Network* net,
		NE_REAL* inputs,
		NE_REAL* outputs );

#ifdef NE_IMPLEMENTATION

/** \brief Returns a random `double` from 0.0 to 1.0.
 *
 * @return A random `double` from 0.0 to 1.0.
 */
double ne_RandomUnit(){
	return (double)rand() / (double)RAND_MAX;
}

/** \brief Returns a random `double` from -1.0 to 1.0.
 *
 * @return A random `double` from -1.0 to 1.0.
 */
double ne_RandomClamped(){
	return ( (double)rand() / (double)RAND_MAX - 0.5 ) * 2.0;
}

/** \brief Returns a stetchy buffer of randomized neural networks.
 *
 * Returns a stretchy buffer of networks with randomized weights
 * centered at 0.0 and a standard deviation of
 * `sqrt( 1.0 / input_weights )`. Biases are initialized to 0.0. As used
 * with SELU.
 *
 * `members` must be a positive even number. If it is not, the program
 * will have undefined behavior.
 *
 * Example:
 * `ne_Population population = ne_RandomNetworks( topology, 20, seed );`
 *
 * @param  topology  Stretchy buffer of network layer sizes from input
 *                   to output. The first value is the number of inputs.
 * @param  members   Number of neural networks to create.
 * @param  rand_seed A seed for srand.
 * @return Stretchy buffer of randomized neural networks.
 */
ne_Population ne_RandomNetworks(
		int* topology,
		int members,
		unsigned int rand_seed ){

	static const ne_Network NewNetwork = { 0.0, NULL };
	static const ne_Neuron NewNeuron = { 0.0, 0.0, NULL };

	srand( rand_seed );

	ne_Population population = NULL;

	// ne_Iterate through the population.
	for( int m = 0; m < members; m++ ){
		// Add a network.
		stb_sb_push( population, NewNetwork );
		ne_Network* net = &stb_sb_last( population );
		// Set the first layer's number of weights per neuron to the number of inputs.
		int input_weights = topology[0];
		// Iterate through the layers.
		for( int i = 1; i < stb_sb_count( topology ); i++ ){
			int t = topology[i];
			//assert( t >= 1 );
			// Add a layer.
			stb_sb_push( net->layers, NULL );
			ne_Layer* layer = &stb_sb_last( net->layers );
			// Set the standard deviation for the layer's weights.
			NE_REAL stddev = sqrt( 1.0 / input_weights );
			// Iterate through the neurons.
			for( int n = 0; n < t; n++ ){
				// Add a neuron.
				stb_sb_push( *layer, NewNeuron );
				ne_Neuron* neuron = &stb_sb_last( *layer );
				// Add random weights.
				for( int w = 0; w < input_weights; w++ ){
					NE_REAL weight = ne_RandomClamped() * stddev;
					stb_sb_push( neuron->weights, weight );
				}
			}
			input_weights = t;
		}
	}

	return population;
}

/** \brief Deep copy `ne_Network` `src` into empty `ne_Network` `dest`.
 *
 * @param src  Pointer to the source neural network.
 * @param dest Pointer to the destination neural network.
 */
void ne_CopyNetwork(
		ne_Network* src,
		ne_Network* dest ){

	//assert( dest->layers == NULL );
	dest->fitness = src->fitness;
	dest->layers = stb_sb_add( dest->layers, stb_sb_count( src->layers ) );
	for( int x = 0; x < stb_sb_count( src->layers ); x++ ){
		ne_Layer* src_layer = &src->layers[x];
		ne_Layer* dest_layer = &dest->layers[x];
		*dest_layer = NULL;
		*dest_layer = stb_sb_add( *dest_layer, stb_sb_count( *src_layer ) );
		for( int y = 0; y < stb_sb_count( *src_layer ); y++ ){
			ne_Neuron* src_neuron = &(*src_layer)[y];
			ne_Neuron* dest_neuron = &(*dest_layer)[y];
			dest_neuron->gradient = src_neuron->gradient;
			dest_neuron->bias = src_neuron->bias;
			dest_neuron->weights = NULL;
			dest_neuron->weights = stb_sb_add(
				dest_neuron->weights,
				stb_sb_count( src_neuron->weights )
			);
			for( int i = 0; i < stb_sb_count( src_neuron->weights ); i++ ){
				dest_neuron->weights[i] = src_neuron->weights[i];
			}
		}
	}
}

/** \brief Deep free `ne_Network` `net`.
 *
 * @param net Pointer to the neural network to be freed.
 */
void ne_FreeNetwork( ne_Network* net ){
	static const ne_Network NewNetwork = { 0.0, NULL };
	for( int x = 0; x < stb_sb_count( net->layers ); x++ ){
		ne_Layer* layer = &net->layers[x];
		for( int y = 0; y < stb_sb_count( *layer ); y++ ){
			stb_sb_free( (*layer)[y].weights );
		}
		stb_sb_free( *layer );
	}
	stb_sb_free( net->layers );
	*net = NewNetwork;
}

/** \brief Deep free a population.
 *
 * @param net Pointer to the `ne_Population` to be freed.
 */
void ne_FreePopulation( ne_Population* population ){
	for( int i = 0; i < stb_sb_count( *population ); i++ ){
		ne_FreeNetwork( &(*population)[i] );
	}
	stb_sb_free( *population );
	*population = NULL;
}

/** \brief Breeds a new generation of neural networks.
 *
 * 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 input and output populations will always have the same count,
 * with the exception that passing in a population of negative or
 * non-even count results in undefined behavior.
 *
 * Make sure you set the `fitness` of each network before calling this
 * function.
 *
 * Example:
 * `ne_Iterate( &population, seed, 0.7, 0.001 );`
 *
 * @param population       A stretchy buffer of neural networks with a
 *                         homogenous topology.
 * @param rand_seed        A seed for `srand`.
 * @param crossover_rate   The probability that 2 chosen networks will
 *                         swap weights.
 * @param mutation_rate    A weight's probability of mutating.
 * @param mutate_gradients Whether or not to allow `gradient` values to
 *                         mutate.
 */
void ne_Iterate(
		ne_Population* population,
		unsigned int rand_seed,
		double crossover_rate,
		double mutation_rate,
		int mutate_gradients ){

	static const ne_Network NewNetwork = { 0.0, NULL };

	// Get the number of network inputs.
	int num_inputs = stb_sb_count( (*population)[0].layers[0][0].weights );

	// Recover the total number of weights from the first network.
	int
		num_weights = 0,
		input_weights = num_inputs;
	for( int i = 0; i < stb_sb_count( (*population)[0].layers ); i++ ){
		int layer_size = stb_sb_count( (*population)[0].layers[i] );
		// The bias counts as an extra weight.
		num_weights += layer_size * ( input_weights + 1 );
		input_weights = layer_size;
	}

	double total_fitness = 0.0;

	// Add up the fitnesses.
	for( int i = 0; i < stb_sb_count( *population ); i++ ){
		NE_REAL* fit = &(*population)[i].fitness;
		// Set negative or NaN fitnesses to 0, lest they cause a crash.
		if( *fit < 0.0 || isnan( *fit ) ){
			*fit = 0.0;
		}
		total_fitness += (double)( *fit );
	}

	srand( rand_seed );

	ne_Population new_population = NULL;

	// ne_Iterate through the population, producing 2 new members for every 2 old members.
	for( int half_n = 0; half_n < stb_sb_count( *population ) / 2; half_n++ ){
		// Create 2 networks.
		ne_Network
			net1 = NewNetwork,
			net2 = NewNetwork;
		// Pick 2 mates using roulette wheel selection.
		double
			slot = 0.0,
			ball1 = ne_RandomUnit() * total_fitness,
			ball2 = ne_RandomUnit() * total_fitness;
		for( int i = 0; i < stb_sb_count( *population ); i++ ){
			ne_Network* p = &(*population)[i];
			if( ball1 >= slot && ball1 <= slot + p->fitness ){
				ne_CopyNetwork( p, &net1 );
			}
			if( ball2 >= slot && ball2 <= slot + p->fitness ){
				ne_CopyNetwork( p, &net2 );
			}
			if( stb_sb_count( net1.layers ) > 0 && stb_sb_count( net2.layers ) > 0 ){
				break;
			}
			slot += fmax( 0.0, (double)p->fitness );
		}

		// Average old fitness for easy access if anything uses it.
		NE_REAL old_fitness = ( net1.fitness + net2.fitness ) * 0.5;
		net1.fitness = old_fitness;
		net2.fitness = old_fitness;

		// Swap weights for crossover_rate of couples.
		if( ne_RandomUnit() <= crossover_rate ){
			// Index weights by order of access as they are not contiguous in memory.
			int
				swap_offset = ne_RandomUnit() * num_weights,
				idx = 0;
			for( int l = 0; l < stb_sb_count( net1.layers ); l++ ){
				if( idx >= swap_offset ){
					break;
				}
				ne_Layer* layer1 = &net1.layers[l];
				ne_Layer* layer2 = &net2.layers[l];
				for( int n = 0; n < stb_sb_count( *layer1 ); n++ ){
					if( idx >= swap_offset ){
						break;
					}
					ne_Neuron* neuron1 = &(*layer1)[n];
					ne_Neuron* neuron2 = &(*layer2)[n];
					// Swap gradients as if they are part of the biases.
					// It is mathematically questionable to group both
					// values as one gene, but the entire neuron depends
					// on the gradient if a backpropagation step is
					// used, so some kind of grouping may be desirable.
					NE_REAL weight_tmp = neuron1->gradient;
					neuron1->gradient = neuron2->gradient;
					neuron2->gradient = weight_tmp;
					// Swap biases.
					weight_tmp = neuron1->bias;
					neuron1->bias = neuron2->bias;
					neuron2->bias = weight_tmp;
					idx++;
					for( int w = 0; w < stb_sb_count( neuron1->weights ); w++ ){
						if( idx >= swap_offset ){
							break;
						}
						// Swap weights.
						weight_tmp = neuron1->weights[w];
						neuron1->weights[w] = neuron2->weights[w];
						neuron2->weights[w] = weight_tmp;
						idx++;
					}
				}
			}
		}

		// Starting number of weights per neuron.
		input_weights = num_inputs;

		// Mutate the weights.
		for( int l = 0; l < stb_sb_count( net1.layers ); l++ ){
			ne_Layer* layer1 = &net1.layers[l];
			ne_Layer* layer2 = &net2.layers[l];
			// Set the standard deviation for the layers' weights.
			NE_REAL stddev = sqrt( 1.0 / input_weights );
			for( int n = 0; n < stb_sb_count( *layer1 ); n++ ){
				ne_Neuron* neuron1 = &(*layer1)[n];
				ne_Neuron* neuron2 = &(*layer2)[n];
				// If a particle hits a real, re-randomize it.
				if( mutate_gradients ){
					// TODO: Investigate different standard deviations.
					if( ne_RandomUnit() <= mutation_rate ){
						neuron1->gradient = ne_RandomClamped() * stddev;
					}
					if( ne_RandomUnit() <= mutation_rate ){
						neuron2->gradient = ne_RandomClamped() * stddev;
					}
				}
				if( ne_RandomUnit() <= mutation_rate ){
					neuron1->bias = ne_RandomClamped() * stddev;
				}
				if( ne_RandomUnit() <= mutation_rate ){
					neuron2->bias = ne_RandomClamped() * stddev;
				}
				for( int w = 0; w < stb_sb_count( neuron1->weights ); w++ ){
					if( ne_RandomUnit() <= mutation_rate ){
						neuron1->weights[w] = ne_RandomClamped() * stddev;
					}
					if( ne_RandomUnit() <= mutation_rate ){
						neuron2->weights[w] = ne_RandomClamped() * stddev;
					}
				}
			}
			input_weights = stb_sb_count( *layer1 );
		}

		// Add the networks to the new population.
		stb_sb_push( new_population, net1 );
		stb_sb_push( new_population, net2 );
	}

	for( int i = 0; i < stb_sb_count( *population ); i++ ){
		// Deep free the old data.
		ne_FreeNetwork( &(*population)[i] );
		// Shallow copy the memory address.
		(*population)[i] = new_population[i];
	}
}

/** \brief Runs data through a neural network.
 *
 * This is the main neural network function. It ingests an array of
 * values and outputs an array of other values that hopefully do what
 * you want them to do.
 *
 * The quality of the outputs depends on the quality of the neural
 * network. If you `ne_Iterate` the population the right number of times
 * with a smartly constructed fitness function and a good enough network
 * topology, then run the fittest member through `ne_GetOutputs`, you
 * should get high-quality outputs from your inputs.
 *
 * If either your `inputs` or `outputs` array is not large enough for
 * the network, you could get access violations, segmentation faults, or
 * undefined behavior. The library has no way to check the size of these
 * arrays.
 *
 * Stretchy buffers can be used instead of arrays for dynamic sizes of
 * `inputs` and `outputs`. (Think scripting and dynamic topologies.)
 * However, this function never checks the counts of these buffers.
 *
 * Example:
 * `ne_GetOutputs( &net, inputs, outputs );`
 *
 * @param net     Pointer to a neural network.
 * @param inputs  Array of `NE_REAL` numbers to be fed into the
 *                network's input layer. All numbers are valid. The size
 *                of the array must be at least the size of the
 *                network's input layer.
 * @param outputs Array of `NE_REAL` numbers to be filled by the
 *                network. The size of the array must be at least the
 *                size of the network's output layer.
 */
void ne_GetOutputs(
		ne_Network* net,
		NE_REAL* inputs,
		NE_REAL* outputs ){

	// SELU constants.
	static const NE_REAL a = 1.6732632423543772848170429916717;
	static const NE_REAL s = 1.0507009873554804934193349852946;

	// Get the number of network inputs.
	int num_inputs = stb_sb_count( net->layers[0][0].weights );

	// Create temporary input and output buffers.
	NE_REAL* in_tmp = NULL;
	NE_REAL* out_tmp = NULL;
	// The inputs array is assumed to be large enough to contain the
	// network's inputs. This keeps the API syntax simple.
	// Copy inputs into in_tmp.
	in_tmp = stb_sb_add( in_tmp, num_inputs );
	for( int i = 0; i < num_inputs; i++ ){
		in_tmp[i] = inputs[i];
	}

	for( int x = 0; x < stb_sb_count( net->layers ); x++ ){
		ne_Layer* layer = &net->layers[x];
		for( int y = 0; y < stb_sb_count( *layer ); y++ ){
			ne_Neuron* neuron = &(*layer)[y];
			// "treat the threshold as a weight that is always
			//  multiplied by an input of -1. This is usually referred
			//  to as the bias."
			NE_REAL activation = -1.0 * neuron->bias;
			for( int i = 0; i < stb_sb_count( neuron->weights ); i++ ){
				activation += in_tmp[i] * neuron->weights[i];
			}
			// SELU
			if( activation < 0.0 ){
				activation = a * exp( activation ) - a;
			}
			activation *= s;
			stb_sb_push( out_tmp, activation );
		}
		stb_sb_free( in_tmp );
		// Shallow copy the memory address.
		in_tmp = out_tmp;
		out_tmp = NULL;
	}

	// The outputs array is assumed to be large enough to contain the
	// network's outputs.
	for( int i = 0; i < stb_sb_count( in_tmp ); i++ ){
		outputs[i] = in_tmp[i];
	}
	stb_sb_free( in_tmp );
}

#endif // #ifdef NE_IMPLEMENTATION

#endif // #ifndef NE_H


Mode Type Size Ref File
100644 blob 98 227abf3bfa53b2530dcc74495da7bd0ccdcb0775 .gitignore
100644 blob 225 9b00c2c2e7b4f0c1e338fdead65f17ba0af089c1 COPYING
100755 blob 43 45aea818a4a3202b2467509f28a481cce08834d2 Confectioner.command
100644 blob 12084 ca1d7e8e522f844c345c314ce6900efe486c41b5 Makefile
100644 blob 2723 b5a3f573f076ef740ca742ec9598043732e10c0e README.md
040000 tree - f854ec266095e18d4bee80b61191f009190533d9 base
100755 blob 156 84cb1387849f2ca98e53e43536d00af2dfabf7d3 caveconfec
100755 blob 28 41b0ef285892c86306eaa269f366dd04cb633d21 caveconfec.bat
100644 blob 195275 7dd3fffe9bb73069112dc76fcecd405fdc0c8235 confec.cpp
100644 blob 391231 cae78a24f09285accda1bd45c3361d92f4d55946 gamecontrollerdb.txt
040000 tree - a24928103d3d2c1929d85ab70447ac50d45a2384 include
100755 blob 1041 dd7c0bd7d8a6b4aeff53142375240872735d42a0 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