Skip to content

quinmars/Evolutionary

Repository files navigation

Evolutionary

Actions Status

Evolutionary algorithms come in many different flavors. The representation of the population's individuals highly depends on the problem space. There are many selection, mutation and recombination mechanism and strategies to choose from. And there are even more possibilities to parameterize them. Even the termination of the population evolution can happen on different aspects. There are libraries (for example the great GeneticSharp) that try to put all those requirements into one procedure, that can be parametrized from outside. The Evolutionary library goes another approach in that it does not implement one or more evolutionary algorithms for you, but gives you the data types and operators at hand to write it yourself.

Therefore Evolutionary uses for the main logic only two immutable types that are transformed by operators. All operators are implemented as extensions method. Hence, it is easy to add custom operators. The Population<TIndividual> class comprises the collection of all individuals, the fitness function and a random number generator. The Offspring<TIndividual> generation happens with the offspring class. Besides a reference to the parent populations, it offers a random enumeration of the parent individuals and an enumerations of the children generated so far. Once the generation phase is finished, a new population will be generated from the offspring.

A complete example

In this example the square root of 2 is calculated. Of course, there are better methods to do this, but it gives a good impression how the Evolutionary library is be used.

using Evolutionary;
using System;

// The data set is the square value of the value we are looking for. If you
// want for example calculate the square root of 5, change the value of the
// data set to 5.
double dataSet = 2;

// The fitnes function calculates how close we are to the exact solution.
Func<double, double> fitness = x => Math.Abs(dataSet - x * x);

// We start with a population of 20 random individuals. Individuals are
// generated by the lambda expression and are uniformly distributed
// between 0 and d, here 2.
var population = Population
    .Create(fitness)
    .AddRandomIndividuals(20, rnd => rnd.NextDouble(0, dataSet));

// We will iterate here over 50 generations. A more sophisticated approach
// would be to consider the development of the fitness value of the best
// individual or the whole population. One could also define an upper limit
// of the calculation time, or a combination of several conditions.
for (int i = 0; i < 50; i++)
{
    population = population
        .SelectParentsByRank()  // This generates an offspring of the population
        .Recombine(
            20,                 // 20 children are created by recombination
            (a, b, rnd) =>      // Two parents, a and b, will create a new child
            {
                // The new child will be located somewhere
                // "between" the parents. Since we use a
                // random value normally distribute around
                // the parents mean value (mu), the child will
                // infact lay in around 30% of the cases
                // outside of the parents' range.
                var mu = (a + b) / 2;
                var sigma = (a == b) ? 1.0E-6 : Math.Abs(a - b) / 2;

                return Math.Abs(rnd.Normal(mu, sigma));
            })
        .SelectElite(2)         // We preserve the two best individuals
                                // of the parent generation.
        .SelectSurvivors(20)    // We have 22 children in this offspring,
                                // but only the best 20 will survive.
        .ToPopulation();        // This will convert the offspring to
                                // a new population.

    // The individuals of a population are always sorted by their fitness.
    // Hence selecting the first individual will give you the currently best
    // fitting individual.
    var best = population.Individuals[0];
    var f = fitness(best);

    Console.WriteLine(
        $"({i}) " +
        $"Best: {best:G17} " + 
        $"Fitness: {f:G5} " +
        $"Expected: {Math.Sqrt(dataSet):G17}");
}

TODO

  • API code documentation
  • generate documentation with docfx
  • add examples
  • add basic Individuals (Floating point string, etc.)

About

A library helping to implement evolutionary alogrithms

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages