source: framspy/FramsticksEvolution.py @ 1117

Last change on this file since 1117 was 1117, checked in by Maciej Komosinski, 3 years ago

Cosmetic

File size: 5.8 KB
Line 
1import argparse
2import os
3import sys
4import numpy as np
5from deap import creator, base, tools, algorithms
6from FramsticksLib import FramsticksLib
7
8# Note: this may be less efficient than running the evolution directly in Framsticks, so if performance is key, compare both options.
9
10
11# The list of criteria includes 'vertpos', 'velocity', 'distance', 'vertvel', 'lifespan', 'numjoints', 'numparts', 'numneurons', 'numconnections'.
12OPTIMIZATION_CRITERIA = ['velocity']  # Single or multiple criteria. Names from the standard-eval.expdef dictionary, e.g. ['vertpos', 'velocity'].
13
14
15def frams_evaluate(frams_cli, individual):
16        genotype = individual[0]  # individual[0] because we can't (?) have a simple str as a deap genotype/individual, only list of str.
17        data = frams_cli.evaluate([genotype])
18        # print("Evaluated '%s'" % genotype, 'evaluation is:', data)
19        try:
20                first_genotype_data = data[0]
21                evaluation_data = first_genotype_data["evaluations"]
22                default_evaluation_data = evaluation_data[""]
23                fitness = [default_evaluation_data[crit] for crit in OPTIMIZATION_CRITERIA]
24        except (KeyError, TypeError) as e:  # the evaluation may have failed for an invalid genotype (such as X[@][@] with "Don't simulate genotypes with warnings" option) or for some other reason
25                fitness = [-1] * len(OPTIMIZATION_CRITERIA)  # fitness of -1 is intended to discourage further propagation of this genotype via selection ("this one is very poor")
26                print('Error "%s": could not evaluate genotype "%s", returning fitness %s' % (str(e), genotype, fitness))
27        return fitness
28
29
30def frams_crossover(frams_cli, individual1, individual2):
31        geno1 = individual1[0]  # individual[0] because we can't (?) have a simple str as a deap genotype/individual, only list of str.
32        geno2 = individual2[0]  # individual[0] because we can't (?) have a simple str as a deap genotype/individual, only list of str.
33        individual1[0] = frams_cli.crossOver(geno1, geno2)
34        individual2[0] = frams_cli.crossOver(geno1, geno2)
35        return individual1, individual2
36
37
38def frams_mutate(frams_cli, individual):
39        individual[0] = frams_cli.mutate([individual[0]])[0]  # individual[0] because we can't (?) have a simple str as a deap genotype/individual, only list of str.
40        return individual,
41
42
43def frams_getsimplest(frams_cli, genetic_format):
44        return frams_cli.getSimplest(genetic_format)
45
46
47def prepareToolbox(frams_cli, genetic_format):
48        creator.create("FitnessMax", base.Fitness, weights=[1.0] * len(OPTIMIZATION_CRITERIA))
49        creator.create("Individual", list, fitness=creator.FitnessMax)  # would be nice to have "str" instead of unnecessary "list of str"
50
51        toolbox = base.Toolbox()
52        toolbox.register("attr_simplest_genotype", frams_getsimplest, frams_cli, genetic_format)  # "Attribute generator"
53        # (failed) struggle to have an individual which is a simple str, not a list of str
54        # toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_frams)
55        # https://stackoverflow.com/questions/51451815/python-deap-library-using-random-words-as-individuals
56        # https://github.com/DEAP/deap/issues/339
57        # https://gitlab.com/santiagoandre/deap-customize-population-example/-/blob/master/AGbasic.py
58        # https://groups.google.com/forum/#!topic/deap-users/22g1kyrpKy8
59        toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_simplest_genotype, 1)
60        toolbox.register("population", tools.initRepeat, list, toolbox.individual)
61        toolbox.register("evaluate", frams_evaluate, frams_cli)
62        toolbox.register("mate", frams_crossover, frams_cli)
63        toolbox.register("mutate", frams_mutate, frams_cli)
64        if len(OPTIMIZATION_CRITERIA) <= 1:
65                toolbox.register("select", tools.selTournament, tournsize=5)
66        else:
67                toolbox.register("select", tools.selNSGA2)
68        return toolbox
69
70
71def parseArguments():
72        parser = argparse.ArgumentParser(description='Run this program with "python -u %s" if you want to disable buffering of its output.' % sys.argv[0])
73        parser.add_argument('-path', type=ensureDir, required=True, help='Path to Framsticks CLI without trailing slash.')
74        parser.add_argument('-lib', required=False, help='Library name. If not given, "frams-objects.dll" or "frams-objects.so" is assumed depending on the platform.')
75        parser.add_argument('-simsettings', required=False, help='The name of the .sim file with settings for evaluation, mutation, crossover, and similarity estimation. If not given, "eval-allcriteria.sim" is assumed by default. Must be compatible with the "standard-eval" expdef.')
76        parser.add_argument('-genformat', required=False, help='Genetic format for the demo run, for example 4, 9, or B. If not given, f1 is assumed.')
77        return parser.parse_args()
78
79
80def ensureDir(string):
81        if os.path.isdir(string):
82                return string
83        else:
84                raise NotADirectoryError(string)
85
86
87if __name__ == "__main__":
88        # A demo run: optimize OPTIMIZATION_CRITERIA
89
90        # random.seed(123)  # see FramsticksLib.DETERMINISTIC below, set to True if you want full determinism
91        FramsticksLib.DETERMINISTIC = False  # must be set before FramsticksLib() constructor call
92        parsed_args = parseArguments()
93        framsLib = FramsticksLib(parsed_args.path, parsed_args.lib, parsed_args.simsettings)
94
95        toolbox = prepareToolbox(framsLib, '1' if parsed_args.genformat is None else parsed_args.genformat)
96
97        POPSIZE = 20
98        GENERATIONS = 50
99
100        pop = toolbox.population(n=POPSIZE)
101        hof = tools.HallOfFame(5)
102        stats = tools.Statistics(lambda ind: ind.fitness.values)
103        stats.register("avg", np.mean)
104        stats.register("stddev", np.std)
105        stats.register("min", np.min)
106        stats.register("max", np.max)
107
108        print('Evolution with population size %d for %d generations, optimization criteria: %s' % (POPSIZE, GENERATIONS, OPTIMIZATION_CRITERIA))
109        pop, log = algorithms.eaSimple(pop, toolbox, cxpb=0.2, mutpb=0.9, ngen=GENERATIONS, stats=stats, halloffame=hof, verbose=True)
110        print('Best individuals:')
111        for best in hof:
112                print(best.fitness, '\t-->\t', best[0])
Note: See TracBrowser for help on using the repository browser.