Skip to content

Using an Environment

Zvi Rosenfeld edited this page Aug 20, 2019 · 4 revisions

Let's see how we can use an environment to our search.

Defining the problem

For this tutorial, we won't be solving a problem. Instead, we'll see how we can use the search engine to simulate a habitat.

Our habitat will have two types of chromosomes in it. Oc2Producers - who consume oxygen (O), and produce carbon dioxide (CO2), and OProducers - who consume carbon dioxide (CO2), and produce oxygen (O). So that our habitat will work, all chromosomes will create more then they consume (that is, if a OProducer consumes 1 CO2, it will produce 2 Os).

Let's start with an enum that will define the types of chromosomes:

enum ChromosomeType
{
   OProducer,
   Oc2Producer
}

Creating the Chromosomes

Our habitat will contain 2 type of chromosomes: OProducer, Oc2Producer. The chromosome will get its type in its constructor.

    class MyChromosome : IChromosome
    {
        private readonly Random random = new Random();

        public ChromosomeType Type { get; private set; }

        public MyChromosome(ChromosomeType type)
        {
            Type = type;
        }

        public double Evaluate()
        {
            // We don't need to implement this. Evaluations will be handled by our custom ChromosomeEvaluator.
            throw new NotImplementedException();
        }
        
        /// <summary>
        /// A mutation will change the type of a chromosome
        /// </summary>
        public void Mutate()
        {
            Type = Type == ChromosomeType.OProducer ? ChromosomeType.Oc2Producer : ChromosomeType.OProducer;
        }

        public override string ToString() => Type.ToString();
    }

Creating the CrossoverManager

Our crossover manager will operate as following:

  • If both chromosomes are OProducers: return a chromosome of type OProducer.
  • If both chromosomes are Oc2Producer : return a chromosome of type Oc2Producer.
  • In any other case: return an OProducerswith a probability of 0.5, and an Oc2Producer with a probability of 0.5.
    class CrossoverManager : ICrossoverManager
    {
        private readonly Random random = new Random();

        public IChromosome Crossover(IChromosome chromosome1, IChromosome chromosome2)
        {
            var type1 = ((MyChromosome) chromosome1).Type;
            var type2 = ((MyChromosome) chromosome2).Type;

            if (type1 == ChromosomeType.OProducer && type2 == ChromosomeType.OProducer)
                return new MyChromosome(ChromosomeType.OProducer);
            if (type1 == ChromosomeType.Oc2Producer && type2 == ChromosomeType.Oc2Producer)
                return new MyChromosome(ChromosomeType.Oc2Producer);

            return new MyChromosome(random.NextDouble() < 0.5 ? ChromosomeType.OProducer : ChromosomeType.Oc2Producer);
        }
    }

Creating the PopulationGenerator

Our PopulationGenerator will generate chromosomes of type OProducers with a probability of 0.5, and one of type Oc2Producers with a probability of 0.5

    class PopulationGenerator : IPopulationGenerator
    {
        private readonly Random random = new Random();

        public IEnumerable<IChromosome> GeneratePopulation(int size)
        {
            var chromosomes = new IChromosome[size];
            for (int i = 0; i < size; i++)
                chromosomes[i] = new MyChromosome(random.NextDouble() < 0.5
                    ? ChromosomeType.OProducer
                    : ChromosomeType.Oc2Producer);

            return chromosomes;
        }
    }

Creating the Environment

Our environment will hold the amounts of O and OC2 available. To calculate this, it will go through the chromosomes in the population, and for every OProducer add 2 Os and reduce one OC2. For every Oc2Producer it will add 2 OC2s and reduce one O.

    class MyEnvironment : IEnvironment
    {
        public void UpdateEnvierment(IChromosome[] chromosomes, int generation)
        {
            O = 0;
            OC2 = 0;

            foreach (var chromosome in chromosomes)
            {
                var myChromosome = (MyChromosome) chromosome;
                if (myChromosome.Type == ChromosomeType.OProducer)
                {
                    O += 2;
                    OC2 -= 1;
                }
                else
                {
                    OC2 += 2;
                    O -= 1;
                }
            }

            O = Math.Max(0, O);
            OC2 = Math.Max(0, OC2);
        }

        public int O { get; private set; }

        public int OC2 { get; private set; }

        public override string ToString() => $"O: {O}; OC2 {OC2}";

Creating the ChromosomeEvaluator

Let's create the ChromosomeEvaluator. The ChromosomeEvaluator will evaluate the chromosomes based on the O and OC2 in the environment.

    class ChromosomeEvaluator : IChromosomeEvaluator
    {
        private MyEnvironment environment;

        public void SetEnvierment(IEnvironment environment)
        {
            this.environment = (MyEnvironment)environment;
        }

        public double Evaluate(IChromosome chromosome)
        {
            var type = ((MyChromosome) chromosome).Type;

            double baseEvaluation = type == ChromosomeType.OProducer ? environment.OC2 : environment.O;
            return (baseEvaluation + 1) / EnvironmentForm.POPULATION_SIZE;
        }

Ruining the Search.

Finally, lets put everything together and run the search:

var engine = new GeneticSearchEngineBuilder(POPULATION_SIZE, GENERATIONS, new CrossoverManager(),
    new PopulationGenerator()).SetCustomChromosomeEvaluator(new ChromosomeEvaluator())
    .SetEnvironment(new MyEnvironment()).SetMutationProbability(0.01).Build();

while (<SomeStopCondition>){
    var result = engine.Next();
    // Do something with the result here.
}

The Complete Code

You can find the complete code (together with a poorly designed GUI), here.

Clone this wiki locally