Genetic OperatorsGiven a representation for a sequence, we need to define basic genetic operators. These are necessary for evolution to be possible, however simple the process. Random GenerationLet's start by considering the random generation of actions, divided into three separate concepts: the type of action, its parameters, and the time offset:
The random generation of sequences must also decide on sequence length (that is, number of actions); this is done randomly, too, but within the limits of a maximal sequence length. Each of the actions can then be generated randomly to occupy each slot in the array. Finally, we have the option of re-offsetting all the times so that the first action starts at 0. This postprocess saves a lot of time in the sequence, although it removes some options for the evolution (for instance, waiting before triggering an action). CrossoverThe simplest possible operator handles crossover: one-point crossover. The idea is to find a random split point on both the parents and swap the subarrays to create two offspring. The advantage of using this approach is that the sequence of genes is left mostly intact, so actions that combine well together have a high chance of still being together after the crossover. This concept is discussed in depth in Chapter 32, "Genetic Algorithms," and this operator presents no particular programming challenge either. MutationThe random mutation happens in two ways. First, with a particularly low probability, the length of a sequence can be modified. If the array needs additional elements, they are added randomly; otherwise they are removed. Mutation also happens to individual actions, with a small probability (different to the one previously discussed). The gene storing the time is mutated by adding a floating-point value generated by a Gaussian distribution (like the parameters). The action is mutated by selecting a different action randomly. If there is a change in action, the parameters are automatically changed to match. |