```'''
Created on Dec 13, 2010

@author: Henrik Rudstrom

`we evolve for two input bits. When a successful solution is found,
the fitness function requires that the program produces a two bit
circuit, followed by a three bit circuit. Then when a genotype has
been found that solves the two bit problem and on iteration solves
the three bit problem, the fitness function changes so that now in
addition to the previous behaviour the genotype, when iterated twice,
produces a phenotype that solves the four-bit problem. The process
continues in this way until we obtain a phenotype that correctly
implements the required function with 20 inputs. We refer to the
application of each parity or adder as a test case.
Fitness is computed as the number of correctly predicted bits over all
test cases. If the candidate solution fails to find a totally correct
solution for a given input size, it is not tested on other input sizes.
We evolve for 19 test cases (2 inputs to 20 inputs).`
-- "Developments in Cartesian Genetic Programming: self-modifying CGP",
Harding Miller Banzhaf 2010

'''

import random
from random import Random
import time
#import pyximport;
#pyximport.install(pyximport, pyimport, build_dir, build_in_temp, setup_args, reload_support)
from ecspy import ec, terminators
from ecspy.contrib.smcgp.encoding import SMCGPEncoding
from ecspy.contrib.smcgp.phenotype import io_functions, SMCGPPhenotype
from ecspy.contrib.smcgp import modification_functions

def wrap(name, func):
def f(g,a,b):
return func(g,a,b)
f.__name__ = name
return f

user_functions = [
wrap("BAND", lambda g,a,b: a and b),
wrap("BOR", lambda g,a,b: a or b),
wrap("BNAND", lambda g,a,b: not  (a and b)),
wrap("BXOR",  lambda g,a,b: (a and not  b) or (b and not  a)),
wrap("BNOR",  lambda g,a,b: not  (a or b)),
wrap("BNOT", lambda g,a,b: not a),
wrap("BIAND", lambda g,a,b: (not  a) and b),
wrap("BF0", lambda g,a,b: False),
wrap("BF1", lambda g,a,b: (a and b)),
wrap("BF2", lambda g,a,b: a and (not  b)),
wrap("BF3", lambda g,a,b: (a and (not b))or(a and b)),
wrap("BF4", lambda g,a,b: (not  a) and b),
wrap("BF5", lambda g,a,b: ((not a) and b) or (a and b)),
wrap("BF6", lambda g,a,b: ((not  a) and b) or (a and (not  b))),
wrap("BF7", lambda g,a,b: ((not  a) and b) or (a and not (b)) or (a and b)),
wrap("BF8", lambda g,a,b: ((not  a) and (not  b))),
wrap("BF9", lambda g,a,b: ((not  a) and (not  b)) or (a and b)),
wrap("BF10", lambda g,a,b: ((not  a) and (not  b)) or (a and not  (b))),
wrap("BF11", lambda g,a,b: ((not a) and (not b) or a and (not b) or a and b)),
wrap("BF12", lambda g,a,b: ((not  a) and (not  b) or (not  a) and b)),
wrap("BF13", lambda g,a,b: ((not a) and (not b) or (not a) and b or a and b)),
wrap("BF14", lambda g,a,b: ((not a) and (not b) or (not a) and b or a and (not b))),
wrap("BF15", lambda g,a,b: ((not a) and (not b) or (not a) and b or a and (not b) or a and b))
]

enc = SMCGPEncoding(length=25, max_arity=2, levelsback=15, min_outputs=1)
enc.set_functions(io_functions+user_functions+modification_functions.all_functions)

def int2bin(n, count=24):
"""returns the binary of integer n, using count number of digits"""
return [(n >> y) & 1 for y in range(count-1, -1, -1)]

def get_test_cases(max_bits):
return [[int2bin(i, bits) for i in xrange(2**bits)] for bits in xrange(2,max_bits+2)]

def get_targets(all_cases):
return [[not sum(case) % 2 for case in cases] for cases in all_cases]

def select_test_cases(all_cases, all_targets, max_cases=100):
cases = []
targets = []
for case, target in zip(all_cases, all_targets):
if len(case) < max_cases:
cases.append(case)
targets.append(target)
else:
sel = list(xrange(len(case)))
random.shuffle(sel)
cases.append([case[s] for s in sel[:max_cases]])
targets.append([target[s] for s in sel[:max_cases]])
return cases, targets

def test_test_cases():
ac = get_test_cases(10)
tac = get_targets(ac)
lac = [len(c) for c in ac]
print lac
assert lac == [4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048]
sac,stac = select_test_cases(ac, tac, 100)
lsac = [len(c) for c in sac]
print lsac
assert lsac == [4, 8, 16, 32, 64, 100, 100, 100, 100, 100]
for c,t in zip(sac, stac):
assert len(c) % 2 == 0

test_test_cases()

all_cases = get_test_cases(10)
all_targets = get_targets(all_cases)
def fitness(candidates, args):
sel_cases, sel_targets = select_test_cases(all_cases, all_targets, 100)
fitness = []
for cand in candidates:
fit = 0
func = SMCGPPhenotype(enc, cand)
iii=0
for cases, targets in zip(sel_cases, sel_targets):

iii += 1
func = func.get_modified()
if not func.is_valid():
break
case_fit = 0
for case, target in zip(cases, targets):
solution = func(case)[0]
if solution == target:
#print solution, target, case
case_fit += 1
fit += case_fit
if case_fit != len(cases):
break
#print "cases", iii
fitness.append(fit)
return fitness

def screen_observer(enc):
def screen_observer(population, num_generations, num_evaluations, args):
import numpy

population = list(population)
population.sort(reverse=True)
worst_fit = population[-1].fitness
best_fit = population[0].fitness
med_fit = numpy.median([p.fitness for p in population])
avg_fit = numpy.mean([p.fitness for p in population])
std_fit = numpy.std([p.fitness for p in population], ddof=1)
if(num_generations % 50 != 0):
return
print('Generation Evaluation Worst      Best       Median     Average    Std Dev   ')
print('---------- ---------- ---------- ---------- ---------- ---------- ----------')
print('{0:10} {1:10} {2:10} {3:10} {4:10} {5:10} {6:10}\n'.format(num_generations, num_evaluations, worst_fit, best_fit, med_fit, avg_fit, std_fit))
print('Current Population: (id,fitness,todo,2 bit solution)')
print('----------------------------------------------------------------------------')
for i, c in enumerate(population):
#f = CGPPhenotype(enc, c.candidate)
#exp = f([Symbol('x'), RealNumber(1.0)])
pass
for i, c in enumerate(population):
#f = CGPPhenotype(enc, c.candidate)
#exp = f([Symbol('x'), RealNumber(1.0)])
phe = SMCGPPhenotype(enc, c.candidate)
#print i, c.fitness, phe.todo_list, phe
print i, c.fitness, [p.__name__ for p in phe.todo_list], phe.get_modified()

return screen_observer

if __name__ == '__main__':

mutator = enc.mutator()
generator = enc.generator()
#seeds = generate_initial_population(generator, 500, 4, fitness_function)

prng = Random()
prng.seed(time.time())
ga = ec.ES(prng)

ga.observer = [screen_observer(enc)]
ga.terminator = terminators.evaluation_termination
ga.variator = [mutator]
#ga.selector = tournament_selection
start = time.time()
#print "setup", seeds

final_pop = ga.evolve(#seeds = seeds,
evaluator=fitness,
generator=generator,
max_evaluations=100000000,
pop_size=5,
mutation_rate=0.066,
maximize=True,
use_one_fifth_rule=True)
stop = time.time()

print('***********************************')
print('Total Execution Time: %0.5f seconds' % (stop - start))

```