Source code for main

'''
This module is intended to duplicate the 'main()' function found in other
languages such as C++ and Java.  In order to run an experiment, this module
should be passed to your interpreter.  In the interest of speed and consistency
we suggest that PyPy 1.9.0 be used to run this code, although
Python 2.7 should be able to handle it correctly.

To see a full description of this modules command line arguments, run
``pypy main.py -h``.

Provided with this code should be the ``cfg`` folder which contains some
configuration files useful for running experiments.  These files can be passed
to main along with other configuration information in order to recreate
the experiments performed in the paper ``Analysis of Cartesian Genetic Programming's
Evolutionary Mechanisms``.  For example, the following command performs
one run of the Parity problem using seed number 13 with a genome size of
2000.  Also, verbose output is printed to the display,
the ``reorder`` variant is used, and the results are output to output/test_run.dat.

``pypy main.py cfg/once.cfg cfg/parity.cfg -seed 13 -g 200 -v -ordering reorder
-out test_run.dat``

For any support questions email brianwgoldman@acm.org.
'''

from evolution import Individual, multi_indepenedent
import problems
import util
from collections import defaultdict


[docs]def one_run(evaluator, config, frequencies): ''' Performs a single run of the given configuration. Returns a dictionary containing results. Parameters: - ``evaluator``: An object with the function get_fitness that takes an individual and returns its fitness value - ``config``: A dictionary containing all of the configuration information required to perform a experimental run, including: - All information required to initialize an individual. - All information required to run ``evolution.multi_independent``. - ``verbose``: Boolean value for if extra runtime information should be displayed. - ``max_evals``: The maximum number of evaluations allowed before termination. - ``max_fitness``: The fitness required to cause a "successful" termination. - ``frequencies``: Dictionary used to return information about how often individuals of different lengths are evolved. Set by evolution.generate. ''' best = None last_improved = -1 output = {'bests': []} generator = enumerate(multi_indepenedent(config, output, frequencies)) for evals, individual in generator: individual.fitness = evaluator.get_fitness(individual) if best < individual: best = individual last_improved = evals if config['record_bests']: save = best.dump() save['evals'] = evals output['bests'].append(save) output['test_inputs'] = sorted(best.input_order.keys(), key=best.input_order.__getitem__) if config['verbose']: print '\t', last_improved, best.fitness, len(best.active) if (evals >= config['max_evals'] or best.fitness >= config['max_fitness']): break if config['verbose']: print "Best Found" print 'Before simplify', best.fitness, len(best.active) best.show_active() simplified = best.new(Individual.simplify) simplified.fitness = evaluator.get_fitness(simplified) print "After simplify", simplified.fitness, len(simplified.active) simplified.show_active() output.update({'fitness': best.fitness, 'evals': evals, 'success': best.fitness >= config['max_fitness'], 'phenotype': len(best.active), 'normal': output['skipped'] + evals, 'unused': sum(best.never_active)}) return output
[docs]def all_runs(config): ''' Perform all of the requested runs on a given problem. Returns a two part tuple: - list of the dictionaries returned by ``one_run``. - frequency information collected by ``one_run``. Will give results for all completed runs if a keyboard interrupt is received. Parameters: - ``config``: Dictionary containing all configuration information required to perform all of the necessary runs of the experiment. Should contain values for: - All configuration information needed by ``one_run`` - ``problem``: The name of which problem from the ``problem`` module to run experiments on. - ``runs``: How many runs to perform ''' # Construct the problem object evaluator = problems.__dict__[config['problem']](config) # Set configuration information from the problem config['function_list'] = evaluator.operators config['max_arity'] = evaluator.max_arity results = [] frequencies = defaultdict(int) try: for run in range(config['runs']): print "Starting Run", run + 1 result = one_run(evaluator, config, frequencies) print [(key, result[key]) for key in ['evals', 'fitness']] results.append(result) except KeyboardInterrupt: print "Interrupted" return results, frequencies
[docs]def combine_results(results): ''' Given a list of result dictionaries, return analysis information such as the median values of each statistic as well as the median absolute deviation. Parameters: - ``results``: A list of dictionaries containing similar key values. ''' combined = {} # Collect the successful runs successful = [result for result in results if result['success']] # Combine like keys across all runs for result in results: for key, value in result.iteritems(): try: combined[key].append(value) except KeyError: combined[key] = [value] # Analyze the values for each key for key, value in combined.items(): try: combined[key] = util.median_deviation(value) except TypeError: del combined[key] try: combined['success'] = len(successful) / float(len(results)), 0 except ZeroDivisionError: combined['success'] = 0, 0 return combined
[docs]def frequencies_to_vector(config, frequencies): ''' Returns a flattened version of the frequencies dictionary. ''' return [frequencies[index] for index in range(config['graph_length'])]
if __name__ == '__main__': import argparse import random import sys # Set up argument parsing description = 'Cartesian Genetic Programming. In Python!' parser = argparse.ArgumentParser(description=description) parser.add_argument('configs', metavar='Configuration Files', type=str, nargs='+', help='Zero or more json formatted files containing' + ' configuration information') parser.add_argument('-g', dest='graph_length', type=int, help='The number of nodes in the CGP graph') parser.add_argument('-m', dest='mutation_rate', type=float, help='Use the specified mutation rate.') parser.add_argument('-i', dest='input_length', type=int, help='The number of input nodes in the CGP graph') parser.add_argument('-o', dest='output_length', type=int, help='The number of output nodes in the CGP graph') parser.add_argument('-pop_size', dest='pop_size', type=int, help='Use the specified population size.') parser.add_argument('-seed', dest='seed', type=int, help='Use the specified random seed used') parser.add_argument('-duplicate', dest='duplicate', type=str, help='Specifies if evolution should should avoid' + ' duplicated evaluations. Valid settings are: ' + 'normal, skip, accumulate, single') parser.add_argument('-ordering', dest='ordering', type=str, help='Specifies how to handle node ordering.' + ' Valid settings are: ' + 'normal, reorder, dag') parser.add_argument('-record_bests', dest='record_bests', action='store_true', help='Include this flag to record the full genome' + ' of the first individual to reach each new fitness.') parser.add_argument('-c', dest='output_config', type=str, help='Outputs a single configuration file containing' + ' the entire configuration used in this run') parser.add_argument('-v', dest='verbose', action='store_true', help='Include this flag to increase periodic output') parser.add_argument('-out', dest='output_results', type=str, help='Specify a file to output the results.') parser.add_argument('-freq', dest='frequency_results', type=str, help='Specify a file to output the frequency results.') parser.add_argument('-profile', dest='profile', action='store_true', help='Include this flag to run a profiler') # Perform argument parsing args = parser.parse_args() config = util.load_configurations(args.configs) config['verbose'] = args.verbose config['record_bests'] = args.record_bests if args.seed != None: config['seed'] = args.seed elif 'seed' not in config: config['seed'] = random.randint(0, sys.maxint) random.seed(config['seed']) # Allow the command line to override configuration file specifications if args.graph_length != None: config['graph_length'] = args.graph_length if args.mutation_rate != None: config['mutation_rate'] = args.mutation_rate if args.input_length != None: config['input_length'] = args.input_length if args.output_length != None: config['output_length'] = args.output_length if args.pop_size != None: config['pop_size'] = args.pop_size if args.duplicate != None: config['duplicate'] = args.duplicate if args.ordering != None: config['ordering'] = args.ordering if args.frequency_results != None: config['frequency_results'] = args.frequency_results if args.profile: # When profiling, just run the configuration import cProfile cProfile.run("all_runs(config)", sort=2) sys.exit() try: # Perform the actual run of the experiment raw_results, frequencies = all_runs(config) combined = sorted(combine_results(raw_results).items()) print combined if args.output_results != None: # Output the results, with the combined stuff on the first line util.save_list(args.output_results, [combined] + raw_results) if args.output_config != None: # Serialize function list config['function_list'] = [func.__name__ for func in config['function_list']] # Saves the final configuration as a single file util.save_configuration(args.output_config, config) if args.frequency_results != None: # Saves the frequency information processed = frequencies_to_vector(config, frequencies) util.save_configuration(args.frequency_results, processed) except KeyError as e: print 'You must include a configuration value for', e.args[0]