Skip to content
Greg Sommerville edited this page Aug 9, 2018 · 2 revisions

Evolutionary.Net

Evolutionary.Net is a system to help .NET programmers write artificial intelligence applications using simple C#. It uses an approach called evolutionary computing, which applies concepts from biology (like survival of the fittest, genetic crossover, and genetic mutations) in order to help your software evolve a solution to a problem.

Evolutionary Computing Basics

The basic concepts of Evolutionary Computing are as follows:

  • You have a problem you want to solve, but there is no obvious solution
  • Potential problem solutions can be represented by an expression tree, which is a tree data structure composed of nodes of different types, including constants, variables, functions, and terminal functions.
  • Each tree can be passed data via variable and terminal function nodes before being evaluated
  • A user-supplied fitness function evaluates each candidate expression tree, and returns a numeric value indicating how fit it is
  • The Evolutionary.Net engine will randomly create a population of candidate solutions for the first generation
  • For each generation, the engine will select candidates randomly, but biased towards better fitness
  • Selected candidates can be crossed over in a manner similar to genetic crossover, where two parents contribute genetic material to a child
  • Some candidates can be randomly mutated
  • The process continues with a new generation, and the cycle repeats until some termination condition is reached
  • The best candidate found to that point is then the final answer

Using Evolutionary.Net

To use the Evolutionary.Net system, add a reference to the assembly to your program. Then instantiate an Engine object, specifying two data types - the data type to be used for all nodes, and another data type that will hold problem state data (the exact form of that data is up to you). The constructor for the Engine takes an EngineParameters object, which contains configuration data for the engine.

Here's an example of instantiating the Engine:

using Evolutionary;

class MyDemo {

  var engineParams = new EngineParameters()
  {
    CrossoverRate = 0.95,
    ElitismPercentageOfPopulation = 15,
    IsLowerFitnessBetter = false,
    MutationRate = 0.02,
    PopulationSize = 500,
    TourneySize = 3,
    NoChangeGenerationCountForTermination = 10, 
    RandomTreeMinDepth = 5,
    RandomTreeMaxDepth = 10
  };

  var engine = new Engine<bool, ProblemState>(engineParams);
}

In this case we are creating an engine where each node in the expression tree will be of type bool. The class that stores our problem state data is defined elsewhere, and it's named ProblemState.

Once we define the engine, we need to supply it with the building blocks for the candidate solutions. Those different node types are called the primitive set, and include nodes to allow passing in variable data, nodes with constant data, and more.

Here are the node types, along with the call to create each:

Node Type Use By Notes
Constant .AddConstant(value) For bool expression trees, value can be either true or false. For a numeric tree (say, of type float), it's a number
Variable .AddVariable(name) Pass a string with a variable name. Before evaluating an expression tree, the variable must be set to some value
Function .AddFunction(function, name) Adds a function with 1 - 4 parameters, along with an associated name. Function nodes are the only node type that have children nodes. The function recursively evaluates its child nodes to determine the values to use as parameters to itself.
TerminalFunction .AddTerminalFunction(function, name) Adds a function with 0 parameters that either returns some calculated value from the problem state data, or some other system-level value

Here's an example of specifying a constant, variable, function and terminal function for the primitive set:

  // as specified above, the tree type is bool
  engine.AddConstant(false);
  engine.AddVariable("X");
  engine.AddFunction((x, y) => x && y, "And");
  engine.AddTerminalFunction(IsInQuadrantOne, "IsInQ1");

Once the primitive set has been defined, you specify a function to check the fitness of each candidate:

  engine.AddFitnessFunction((t) => EvaluateCandidate(t));

That fitness function looks something like this:

private float EvaluateCandidate(CandidateSolution<bool, ProblemState> candidate)
{
  // do some calculations using the candidate and return a value indicating fitness
  return something;
}

At this point, all you need to do is ask the engine to find the best solution:

  var BestSolution = engine.FindBestSolution();

Since it may take a while to find a solution, you can also specify a progress callback function, which is passed an object of type EngineProgress. You can return a boolean value of false from this progress function to immediately halt the process.

Please see the other pages in this Wiki for more details, and examine the demo.