250 likes | 355 Views
Evolving a stack. The operation of a stack operation Code Comment makenull sp=maxlength+1 initialise stack empty empty=(sp>maxlength) is stack empty? top top=stack[sp] top of the stack pop pop=stack[sp] return top of stack sp=sp+1 and return it
E N D
Evolving a stack The operation of a stack operation Code Comment makenull sp=maxlength+1 initialise stack empty empty=(sp>maxlength) is stack empty? top top=stack[sp] top of the stack pop pop=stack[sp] return top of stack sp=sp+1 and return it push(x) sp=sp-1 place x on top of stack[sp]=x stack
Some notes Remove the check of a stack underflow or overflow Require the GP to simultaneously co-evolve five operations
Architecture Each individual within the population is a trial implementation of the whole of stack and so implements each of the five operations that a stack supports. Each operation is programmed by its own tree. The complete individual is composed of one tree for each operation making a total of five trees.
Choice of Primitives arg1, the value to be pushed on to the stack. When arg1 is used by any of the operations except push it has the value zero. Evolving programs can read arg1 but they can not change it. arithmetic operators + and - constants 0,1 and the maximum depth of the stack, max (which has the value 10 here) indexed memory functions read and write primitives to help maintain a stack pointer: aux, inc_aux, dec_aux and write_Aux
Choice of Primitives It is decided to evolve a stack of integer values as integers are naturally compatible with addressing operations required with indexed memory and the arithmetic required with a stack and so all primitives can be of the same type (integer). No restrictions are placed upon which primitives could be used by which operation. The terminal and function sets are identical for each of the five trees.
Indexed memory 63 integer memory cells (numbered -31,...,31) are available. This more than sufficient for a stack of no more than 10 integers. Read’s and write’s first argument specifies which of the 63 cells to use. If it yields an illegal value, several strategies may be adopted
Indexed memory 1) fitness testing of the trial program is aborted 2) Reduce the range of integer values to just those that are legal memory addresses. This means changing the closure so that every function returns reduced integer values. 3) Define behaviour for read and write following an address error. 4) Increase the range of legal memory addresses to the range of legal integers. 5) Allow more than one type in the evolving programs; e.g., 32-bit integers for data values and contents of memory cells and a restricted integer for memory addressing
Register In addition to the addressable memory a single auxiliary variable “aux” is provided which, like each indexed memory cell, is capable of storing a single 32-bit signed integer. The motivation for including it and the primitives that manipulate it is that it could be used as a stack pointer, holding addresses to be used with the index memory. The GP is not forced to use it in any particular way or even use it at all
Associated primitives 1) aux, which evaluates to its current value 2) inc_aux, which increase the current value by one and return the new value 3) dec_aux, which decrease the current value by one and return the new value 4) write_Aux, which evaluates its argument and sets aux to this value
Fitness function The fitness of each individual program is evaluated by calling its constituent operations in a series of test sequences and comparing the result they return with the anticipated result. Calling the operation and the subsequent comparison is known as a fitness test. All testing is black box, no information about the program’s internal behaviour such as use of memory is used. However programs which read or write outside the available memory are heavily penalised because their testing stops immediately. The two operations makenull and push do not return an answer. They can only be tested indirectly by seeing if the other operations work correctly when called after them.
Fitness function The stack is defined to exclude error checks and so the fitness test case is designed to avoid causing the logical errors that these checks will trap, i.e., it never causes stack overflow or underflow, top is never called when the stack should be empty and the stack is always initialised by makenull before any other operation is used. All storage, the indexed memory and aux, is initialized to zero before each test sequence is started.
Test case The test case comprised four fixed test sequences each of which called 40 operations and checked the value returned. The four test sequences contain different proportions of each of the five operations.
Test case Although the stack is defined to operate with stacks of up to 10 integers, it is not necessary for the fitness test case to cover the deeper stacks. In fact the fitness function tests only as far as depths of 4 items.
The integer data values pushed onto the stack (i.e., the value of arg1) are generated at random uniformly in the range -1000,..., 999. The 47 actual values used are given in the following
Parameters Population size: 1000 Max generation: 100
Notes Each successful program is a different solution to the problem. Although coded differently, the first three adopted the same strategy of using aux as a stack pointer for a push-down stack with make null initialising aux to one. The fourth also use aux as a stack pointer but initialises it to -1 and adopts a push-up strategy. In all cases the four programs not only pass all the fitness test cases but analysis of their code shows they are general solutions to the defined problem. Each program contains redundant code.
Some bad news Only four of the 60 runs yielded solutions.
Typically the highest fitness score is found by generation 6 and no further improvement in the best score is made in the later 94 generations. The population as a whole rapidly converges to the best with a few individuals having very low fitness values.