Changeset 1161 for framspy/FramsticksEvolution.py
- Timestamp:
- 11/29/21 03:33:54 (3 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
framspy/FramsticksEvolution.py
r1117 r1161 6 6 from FramsticksLib import FramsticksLib 7 7 8 8 9 # Note: this may be less efficient than running the evolution directly in Framsticks, so if performance is key, compare both options. 9 10 10 11 11 # The list of criteria includes 'vertpos', 'velocity', 'distance', 'vertvel', 'lifespan', 'numjoints', 'numparts', 'numneurons', 'numconnections'. 12 OPTIMIZATION_CRITERIA = ['velocity'] # Single or multiple criteria. Names from the standard-eval.expdef dictionary, e.g. ['vertpos', 'velocity']. 12 def genotype_within_constraint(criterion_actual_value, constraint_value): 13 if constraint_value is not None: 14 if criterion_actual_value > constraint_value: 15 return False 16 return True 13 17 14 18 … … 17 21 data = frams_cli.evaluate([genotype]) 18 22 # print("Evaluated '%s'" % genotype, 'evaluation is:', data) 23 valid = True 19 24 try: 20 25 first_genotype_data = data[0] … … 23 28 fitness = [default_evaluation_data[crit] for crit in OPTIMIZATION_CRITERIA] 24 29 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")30 valid = False 26 31 print('Error "%s": could not evaluate genotype "%s", returning fitness %s' % (str(e), genotype, fitness)) 32 if valid: 33 valid &= genotype_within_constraint(default_evaluation_data['numparts'], parsed_args.max_numparts) 34 valid &= genotype_within_constraint(default_evaluation_data['numjoints'], parsed_args.max_numjoints) 35 valid &= genotype_within_constraint(default_evaluation_data['numneurons'], parsed_args.max_numneurons) 36 valid &= genotype_within_constraint(default_evaluation_data['numconnections'], parsed_args.max_numconnections) 37 valid &= genotype_within_constraint(len(genotype), parsed_args.max_numgenochars) 38 if not valid: 39 fitness = [-1] * len(OPTIMIZATION_CRITERIA) # fitness of -1 is intended to discourage further propagation of this genotype via selection ("this genotype is very poor") 27 40 return fitness 28 41 … … 41 54 42 55 43 def frams_getsimplest(frams_cli, genetic_format ):44 return frams_cli.getSimplest(genetic_format)56 def frams_getsimplest(frams_cli, genetic_format, initial_genotype): 57 return initial_genotype if initial_genotype is not None else frams_cli.getSimplest(genetic_format) 45 58 46 59 47 def prepareToolbox(frams_cli, genetic_format):60 def prepareToolbox(frams_cli, tournament_size, genetic_format, initial_genotype): 48 61 creator.create("FitnessMax", base.Fitness, weights=[1.0] * len(OPTIMIZATION_CRITERIA)) 49 62 creator.create("Individual", list, fitness=creator.FitnessMax) # would be nice to have "str" instead of unnecessary "list of str" 50 63 51 64 toolbox = base.Toolbox() 52 toolbox.register("attr_simplest_genotype", frams_getsimplest, frams_cli, genetic_format ) # "Attribute generator"65 toolbox.register("attr_simplest_genotype", frams_getsimplest, frams_cli, genetic_format, initial_genotype) # "Attribute generator" 53 66 # (failed) struggle to have an individual which is a simple str, not a list of str 54 67 # toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_frams) … … 63 76 toolbox.register("mutate", frams_mutate, frams_cli) 64 77 if len(OPTIMIZATION_CRITERIA) <= 1: 65 toolbox.register("select", tools.selTournament, tournsize= 5)78 toolbox.register("select", tools.selTournament, tournsize=tournament_size) 66 79 else: 67 80 toolbox.register("select", tools.selNSGA2) … … 73 86 parser.add_argument('-path', type=ensureDir, required=True, help='Path to Framsticks CLI without trailing slash.') 74 87 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.') 88 parser.add_argument('-sim', required=False, default="eval-allcriteria.sim", 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. If you want to provide more files, separate them with a semicolon ';'.") 89 90 parser.add_argument('-genformat', required=False, help='Genetic format for the simplest initial genotype, for example 4, 9, or B. If not given, f1 is assumed.') 91 parser.add_argument('-initialgenotype', required=False, help='The genotype used to seed the initial population. If given, the -genformat argument is ignored.') 92 93 parser.add_argument('-opt', required=True, help='optimization criteria: vertpos, velocity, distance, vertvel, lifespan, numjoints, numparts, numneurons, numconnections (or other as long as it is provided by the .sim file and its .expdef). For multiple criteria optimization, separate the names by the comma.') 94 parser.add_argument('-popsize', type=int, default=50, help="Population size, default: 50.") 95 parser.add_argument('-generations', type=int, default=5, help="Number of generations, default: 5.") 96 parser.add_argument('-tournament', type=int, default=5, help="Tournament size, default: 5.") 97 parser.add_argument('-pmut', type=float, default=0.9, help="Probability of mutation, default: 0.9") 98 parser.add_argument('-pxov', type=float, default=0.2, help="Probability of crossover, default: 0.2") 99 parser.add_argument('-hof_size', type=int, default=10, help="Number of genotypes in Hall of Fame. Default: 10.") 100 parser.add_argument('-hof_savefile', required=False, help='If set, Hall of Fame will be saved in Framsticks file format (recommended extension *.gen).') 101 102 parser.add_argument('-max_numparts', type=int, default=None, help="Maximum number of Parts. Default: no limit") 103 parser.add_argument('-max_numjoints', type=int, default=None, help="Maximum number of Joints. Default: no limit") 104 parser.add_argument('-max_numneurons', type=int, default=None, help="Maximum number of Neurons. Default: no limit") 105 parser.add_argument('-max_numconnections', type=int, default=None, help="Maximum number of Neural connections. Default: no limit") 106 parser.add_argument('-max_numgenochars', type=int, default=None, help="Maximum number of characters in genotype (including the format prefix, if any). Default: no limit") 77 107 return parser.parse_args() 78 108 … … 85 115 86 116 117 def save_genotypes(filename, OPTIMIZATION_CRITERIA, hof): 118 from framsfiles import writer as framswriter 119 with open(filename, "w") as outfile: 120 for ind in hof: 121 keyval = {} 122 for i, k in enumerate(OPTIMIZATION_CRITERIA): # construct a dictionary with criteria names and their values 123 keyval[k] = ind.fitness.values[i] # TODO it would be better to save in Individual (after evaluation) all fields returned by Framsticks, and get these fields here, not just the criteria that were actually used as fitness in evolution. 124 # Note: prior to the release of Framsticks 5.0, saving e.g. numparts (i.e. P) without J,N,C breaks re-calcucation of P,J,N,C in Framsticks and they appear to be zero (nothing serious). 125 outfile.write(framswriter.from_collection({"_classname": "org", "genotype": ind[0], **keyval})) 126 outfile.write("\n") 127 print("Saved '%s' (%d)" % (filename, len(hof))) 128 129 87 130 if __name__ == "__main__": 88 # A demo run: optimize OPTIMIZATION_CRITERIA89 90 131 # random.seed(123) # see FramsticksLib.DETERMINISTIC below, set to True if you want full determinism 91 132 FramsticksLib.DETERMINISTIC = False # must be set before FramsticksLib() constructor call 92 133 parsed_args = parseArguments() 93 framsLib = FramsticksLib(parsed_args.path, parsed_args.lib, parsed_args.simsettings)134 print("Argument values:", ", ".join(['%s=%s' % (arg, getattr(parsed_args, arg)) for arg in vars(parsed_args)])) 94 135 95 toolbox = prepareToolbox(framsLib, '1' if parsed_args.genformat is None else parsed_args.genformat) 136 OPTIMIZATION_CRITERIA = parsed_args.opt.split(",") 137 framsLib = FramsticksLib(parsed_args.path, parsed_args.lib, parsed_args.sim.split(";")) 96 138 97 POPSIZE = 20 98 GENERATIONS = 50 139 toolbox = prepareToolbox(framsLib, parsed_args.tournament, '1' if parsed_args.genformat is None else parsed_args.genformat, parsed_args.initialgenotype) 99 140 100 pop = toolbox.population(n= POPSIZE)101 hof = tools.HallOfFame( 5)141 pop = toolbox.population(n=parsed_args.popsize) 142 hof = tools.HallOfFame(parsed_args.hof_size) 102 143 stats = tools.Statistics(lambda ind: ind.fitness.values) 103 144 stats.register("avg", np.mean) … … 106 147 stats.register("max", np.max) 107 148 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) 149 pop, log = algorithms.eaSimple(pop, toolbox, cxpb=parsed_args.pxov, mutpb=parsed_args.pmut, ngen=parsed_args.generations, stats=stats, halloffame=hof, verbose=True) 110 150 print('Best individuals:') 111 for best in hof: 112 print(best.fitness, '\t-->\t', best[0]) 151 for ind in hof: 152 print(ind.fitness, '\t-->\t', ind[0]) 153 154 if parsed_args.hof_savefile is not None: 155 save_genotypes(parsed_args.hof_savefile, OPTIMIZATION_CRITERIA, hof)
Note: See TracChangeset
for help on using the changeset viewer.