Index: /framspy/FramsticksCLI.py
===================================================================
--- /framspy/FramsticksCLI.py	(revision 947)
+++ /framspy/FramsticksCLI.py	(revision 948)
@@ -253,4 +253,5 @@
 	# TODO ideas:
 	# - check_validity with three levels (invalid, corrected, valid)
+	# - "vectorize" some operations (isvalid, evaluate) so that a number of genotypes is handled in one call
 	# - use threads for non-blocking reading from frams' stdout and thus not relying on specific strings printed by frams
 	# - a pool of binaries run at the same time, balance load - in particular evaluation
Index: /framspy/FramsticksEvolution.py
===================================================================
--- /framspy/FramsticksEvolution.py	(revision 948)
+++ /framspy/FramsticksEvolution.py	(revision 948)
@@ -0,0 +1,111 @@
+import argparse
+import os
+import sys
+import numpy as np
+from deap import creator, base, tools, algorithms
+from FramsticksCLI import FramsticksCLI
+
+# Note: this is much less efficient than running the evolution directly in Framsticks, so use only when required or when poor performance is acceptable!
+
+
+# The list of criteria includes 'vertpos', 'velocity', 'distance', 'vertvel', 'lifespan', 'numjoints', 'numparts', 'numneurons', 'numconnections'.
+OPTIMIZATION_CRITERIA = ['vertpos']  # Single or multiple criteria. Names from the standard-eval.expdef dictionary, e.g. ['vertpos', 'velocity'].
+
+
+def frams_evaluate(frams_cli, individual):
+	genotype = individual[0]  # [0] because we can't (?) have a simple str as a deap genotype/individual, only list of str.
+	data = frams_cli.evaluate(genotype)
+	# print("Evaluated '%s'" % genotype, 'evaluation is:', data)
+	try:
+		first_genotype_data = data[0]
+		evaluation_data = first_genotype_data["evaluations"]
+		default_evaluation_data = evaluation_data[""]
+		fitness = [default_evaluation_data[crit] for crit in OPTIMIZATION_CRITERIA]
+	except (KeyError, TypeError) as e:  # the evaluation may have failed for invalid genotypes (or some other reason)
+		fitness = [-1] * len(OPTIMIZATION_CRITERIA)
+		print("Error '%s': could not evaluate genotype '%s', returning fitness %s" % (str(e), genotype, fitness))
+	return fitness
+
+
+def frams_crossover(frams_cli, individual1, individual2):
+	geno1 = individual1[0]  # [0] because we can't (?) have a simple str as a deap genotype/individual, only list of str.
+	geno2 = individual2[0]  # [0] because we can't (?) have a simple str as a deap genotype/individual, only list of str.
+	individual1[0] = frams_cli.crossOver(geno1, geno2)
+	individual2[0] = frams_cli.crossOver(geno1, geno2)
+	return individual1, individual2
+
+
+def frams_mutate(frams_cli, individual):
+	individual[0] = frams_cli.mutate(individual[0])
+	return individual,
+
+
+def frams_getsimplest(frams_cli, genetic_format):
+	return frams_cli.getSimplest(genetic_format)
+
+
+def prepareToolbox(frams_cli, genetic_format):
+	creator.create("FitnessMax", base.Fitness, weights=[1.0] * len(OPTIMIZATION_CRITERIA))
+	creator.create("Individual", list, fitness=creator.FitnessMax)  # would be nice to have "str" instead of unnecessary "list of str"
+
+	toolbox = base.Toolbox()
+	toolbox.register("attr_simplest_genotype", frams_getsimplest, frams_cli, genetic_format)  # "Attribute generator"
+	# (failed) struggle to have an individual which is a simple str, not a list of str
+	# toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_frams)
+	# https://stackoverflow.com/questions/51451815/python-deap-library-using-random-words-as-individuals
+	# https://github.com/DEAP/deap/issues/339
+	# https://gitlab.com/santiagoandre/deap-customize-population-example/-/blob/master/AGbasic.py
+	# https://groups.google.com/forum/#!topic/deap-users/22g1kyrpKy8
+	toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_simplest_genotype, 1)
+	toolbox.register("population", tools.initRepeat, list, toolbox.individual)
+	toolbox.register("evaluate", frams_evaluate, frams_cli)
+	toolbox.register("mate", frams_crossover, frams_cli)
+	toolbox.register("mutate", frams_mutate, frams_cli)
+	if len(OPTIMIZATION_CRITERIA) > 1:
+		toolbox.register("select", tools.selTournament, tournsize=5)
+	else:
+		toolbox.register("select", tools.selNSGA2)
+	return toolbox
+
+
+def parseArguments():
+	parser = argparse.ArgumentParser(description='Run this program with "python -u %s" if you want to disable buffering of its output.' % sys.argv[0])
+	parser.add_argument('-path', type=ensureDir, required=True, help='Path to Framsticks CLI without trailing slash.')
+	parser.add_argument('-exe', required=False, help='Executable name. If not given, "frams.exe" or "frams.linux" is assumed.')
+	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.')
+	return parser.parse_args()
+
+
+def ensureDir(string):
+	if os.path.isdir(string):
+		return string
+	else:
+		raise NotADirectoryError(string)
+
+
+if __name__ == "__main__":
+	# A demo run: optimize OPTIMIZATION_CRITERIA
+
+	# random.seed(123)  # see FramsticksCLI.DETERMINISTIC below, set to True if you want full determinism
+	FramsticksCLI.DETERMINISTIC = False  # must be set before FramsticksCLI() constructor call
+	parsed_args = parseArguments()
+	framsCLI = FramsticksCLI(parsed_args.path, parsed_args.exe)
+
+	toolbox = prepareToolbox(framsCLI, '1' if parsed_args.genformat is None else parsed_args.genformat)
+
+	POPSIZE = 10
+	GENERATIONS = 100
+
+	pop = toolbox.population(n=POPSIZE)
+	hof = tools.HallOfFame(5)
+	stats = tools.Statistics(lambda ind: ind.fitness.values)
+	stats.register("avg", np.mean)
+	stats.register("stddev", np.std)
+	stats.register("min", np.min)
+	stats.register("max", np.max)
+
+	print('Evolution with population size %d for %d generations, optimization criteria: %s' % (POPSIZE, GENERATIONS, OPTIMIZATION_CRITERIA))
+	pop, log = algorithms.eaSimple(pop, toolbox, cxpb=0.2, mutpb=0.8, ngen=GENERATIONS, stats=stats, halloffame=hof, verbose=True)
+	print('Best individuals:')
+	for best in hof:
+		print(best.fitness, '\t-->\t', best[0])
Index: /framspy/eval-allcriteria.sim
===================================================================
--- /framspy/eval-allcriteria.sim	(revision 948)
+++ /framspy/eval-allcriteria.sim	(revision 948)
@@ -0,0 +1,366 @@
+# Created Thu Jun 10 11:08:43 2020
+# Sample settings for evaluating many criteria of performance of a Framsticks creature.
+# See the "evalcount" parameter if you want to speed the evaluation up or have a more stable result.
+# Other important parameters are "Energy0" and "perfperiod".
+sim_params:
+stop_on:2
+last_genotype_num:1
+last_creature_num:20
+expdef:standard-eval
+usercode:
+autosaveperiod:0
+overwrite:0
+filecomm:1
+createrr:1
+groupchk:1
+creatwarnfail:1
+vm_step_limit:0
+vm_step_warning:0
+vm_time_limit:0.0
+vm_time_warning:0.0
+version_int:7
+initialgen:X
+capacity:0
+MaxCreated:1
+placement:0
+rotation:0
+creath:0.1
+p_nop:1.0
+p_mut:0.0
+p_xov:0.0
+xov_mins:0.0
+selrule:2
+delrule:0
+cr_c:0.0
+cr_life:0.0
+cr_v:1.0
+cr_gl:0.0
+cr_joints:0.0
+cr_nnsiz:0.0
+cr_nncon:0.0
+cr_di:0.0
+cr_vpos:0.0
+cr_vvel:0.0
+cr_norm:0
+cr_simi:0
+cr_nsga:0
+Energy0:10000.0
+e_meta:1.0
+feed:0
+feede0:200.0
+foodgen:
+feedtrans:1.0
+aging:0
+evalcount:2
+evalplan:~
+:numparts,numjoints,numneurons,numconnections,instances,lifespan,velocity,distance,vertvel,vertpos,fit,fit_stdev,time~
+evalsavedata:evaluation
+evalsavefile:genos_eval.json
+update_nsga_ranks_needed:1
+notes:
+totaltestedcr:1
+wrldtyp:0
+wrldsiz:20.0
+wrldmap:
+wrldwat:-1.0
+wrldbnd:0
+wrldg:1.0
+simtype:0
+nnspeed:1.0
+rndcollisions:1
+odeshape:0
+odestep:0.05
+odemusclemin:0.0
+odemusclemax:10.0
+odemusclespeed:1.0
+odeairdrag:0.01
+oderotdrag:0.01
+odewaterdrag:0.5
+odewaterbuoy:1.0
+odeseed:0
+odesepsticks:0
+odeworlderp:0.2
+odeworldcfm:1e-5
+odecolmumin:0.1
+odecolmumax:5.0
+odecolbounce:0.1
+odecolbouncevel:0.01
+odecolsoftcfm:0.0
+odecolsofterp:0.0
+odecol2mumin:0.1
+odecol2mumax:1.0
+odecol2bounce:0.1
+odecol2bouncevel:0.01
+odecol2softcfm:0.0
+odecol2softerp:0.0
+gen_hilite:1
+gen_extmutinfo:0
+genoper_f0:0
+genoper_f1:0
+genoper_f2:0
+genoper_f3:0
+genoper_f4:0
+genoper_f8:0
+genoper_f9:0
+genoper_fF:0
+genoper_fn:0
+genoper_fB:0
+genoper_fH:0
+genoper_fL:0
+neuadd_N:1
+neuadd_Nu:0
+neuadd_G:1
+neuadd_T:1
+neuadd_S:1
+neuadd_Constant:1
+neuadd_Bend_muscle:1
+neuadd_Rotation_muscle:1
+neuadd_D:0
+neuadd_Fuzzy:0
+neuadd_VEye:0
+neuadd_VMotor:0
+neuadd_Sti:0
+neuadd_LMu:0
+neuadd_Water:0
+neuadd_Energy:0
+neuadd_Ch:0
+neuadd_ChMux:0
+neuadd_ChSel:0
+neuadd_Rnd:0
+neuadd_Sin:0
+neuadd_Delay:0
+neuadd_Light:0
+neuadd_Nn:0
+neuadd_PIDP:0
+neuadd_PIDV:0
+neuadd_SeeLight:0
+neuadd_SeeLight2:0
+neuadd_S0:0
+neuadd_S1:0
+neuadd_Thr:0
+f0_nodel_tag:1
+f0_nomod_tag:1
+f0_p_new:5.0
+f0_p_del:5.0
+f0_p_swp:10.0
+f0_p_pos:10.0
+f0_p_mas:10.0
+f0_p_frc:10.0
+f0_p_ing:10.0
+f0_p_asm:10.0
+f0_p_vsiz:0.0
+f0_j_new:5.0
+f0_j_del:5.0
+f0_j_stm:10.0
+f0_j_stf:10.0
+f0_j_rsf:10.0
+f0_j_vred:0.0
+f0_j_vgrn:0.0
+f0_j_vblu:0.0
+f0_n_new:5.0
+f0_n_del:5.0
+f0_n_prp:10.0
+f0_c_new:5.0
+f0_c_del:5.0
+f0_c_wei:10.0
+f1_xo_propor:1
+f1_smX:0.05
+f1_smJunct:0.02
+f1_smComma:0.02
+f1_smModif:0.1
+f1_mut_exmod:EeWwAaSsDdGgBbHh
+f1_nmNeu:0.05
+f1_nmConn:0.1
+f1_nmProp:0.1
+f1_nmWei:1.0
+f1_nmVal:0.05
+f2_mutAddOper:0.4
+f2_mutJointElem:0.33
+f2_mutNeuroElem:0.33
+f2_mutConnElem:0.33
+f2_mutDelOper:0.1
+f2_mutHandleOper:0.3
+f2_mutPropOper:0.2
+f3_mutSubstitution:0.6
+f3_mutSubstPerChar:0.1
+f3_mutDelIns:0.1
+f3_mutDelInsPerChar:0.05
+f3_mutDelInsLength:5
+f3_mutDuplication:0.05
+f3_mutTranslocation:0.15
+f3_xovGeneTransfer:0.8
+f3_xovCrossingOver:0.2
+f4_mut_add:50.0
+f4_mut_add_div:20.0
+f4_mut_add_conn:15.0
+f4_mut_add_neupar:5.0
+f4_mut_add_rep:10.0
+f4_mut_add_simp:50.0
+f4_mut_del:20.0
+f4_mut_mod:30.0
+f4_mut_exmod:EeWwAaSsDdGgBbHh
+f8_mut_chg_begin_arg:7.0
+f8_mut_chg_arg:7.0
+f8_mut_del_comm:8.0
+f8_mut_insert_comm:8.0
+f8_mut_enc:8.0
+f8_mut_chg_cond_sign:7.0
+f8_mut_add_param:8.0
+f8_mut_add_cond:8.0
+f8_mut_add_subprod:8.0
+f8_mut_chg_iter_number:7.0
+f8_mut_del_param:8.0
+f8_mut_del_cond:8.0
+f8_mut_add_loop:0.0
+f8_mut_del_loop:0.0
+f8_mut_del_prod:8.0
+f9_mut:0.1
+fF_xover:0.5
+fn_xover:0.9
+fn_xover_random:1
+fn_mut_bound_low:[-10.0, -10.0]
+fn_mut_bound_high:[10.0, 10.0]
+fn_mut_stddev:[0.1, 0.1]
+fn_mut_single_var:0
+fB_mut_substitution:0.6
+fB_mut_insertion:0.095
+fB_mut_nclassins:0.005
+fB_mut_deletion:0.1
+fB_mut_duplication:0.0
+fB_mut_translocation:0.15
+fB_cross_gene_transfer:0.0
+fB_cross_crossover:1.0
+fH_mut_addition:0.3
+fH_mut_add_joint:0.33
+fH_mut_add_neuron:0.33
+fH_mut_add_connection:0.33
+fH_mut_deletion:0.1
+fH_mut_handle:0.3
+fH_mut_property:0.3
+fL_maxdefinedwords:10
+fL_axm_mut_prob:0.2
+fL_rul_mut_prob:0.8
+fL_mut_addition:0.2
+fL_mut_add_stick:0.2
+fL_mut_add_neuro:0.2
+fL_mut_add_conn:0.2
+fL_mut_add_rot:0.2
+fL_mut_add_branch:0.2
+fL_mut_add_other:0.4
+fL_mut_worddefaddition:0.05
+fL_mut_ruleaddition:0.1
+fL_mut_rulecond:0.1
+fL_mut_changeword:0.3
+fL_mut_changeword_formula:0.7
+fL_mut_changeword_name:0.3
+fL_mut_changeiter:0.3
+fL_mut_changeiter_step:1.0
+fL_mut_deletion:0.2
+genkonw0:1
+genkonw1:1
+genkonw2:1
+genkonw3:1
+genkonw4:1
+genkonw5:1
+genkonw6:1
+genkonw7:1
+genkonw8:1
+genkonw9:1
+genkonw10:1
+randinit:0.01
+nnoise:0.0
+touchrange:1.0
+bnoise_struct:0.0
+bnoise_vel:0.0
+ncl_N:1
+ncl_Nu:1
+ncl_G:1
+ncl_T:1
+ncl_S:1
+ncl_Constant:1
+ncl_Bend_muscle:1
+ncl_Rotation_muscle:1
+ncl_D:1
+ncl_Fuzzy:1
+ncl_VEye:1
+ncl_VMotor:1
+ncl_Sti:1
+ncl_LMu:1
+ncl_Water:1
+ncl_Energy:1
+ncl_Ch:1
+ncl_ChMux:1
+ncl_ChSel:1
+ncl_Rnd:1
+ncl_Sin:1
+ncl_Delay:1
+ncl_Light:1
+ncl_Nn:1
+ncl_PIDP:1
+ncl_PIDV:1
+ncl_SeeLight:1
+ncl_SeeLight2:1
+ncl_S0:1
+ncl_S1:1
+ncl_Thr:1
+simil_method:0
+simil_parts:0.0
+simil_partdeg:1.0
+simil_neuro:0.1
+simil_partgeom:0.0
+simil_fixedZaxis:0
+simil_weightedMDS:0
+symPosSteps:10
+symAlphaSteps:20
+symBetaSteps:20
+geom_density:1.0
+minjoint:0.0
+maxjoint:2.0
+
+GenePool:
+name:Genotypes
+fitness:return 0.0+this.velocity*1.0;
+fitfun:0
+fitm:2.0
+fitma:2.0
+
+Population:
+name:Creatures
+energy:1
+death:1
+initial_nn_active:2
+nn_paused:0
+perfperiod:1000
+stabilperiod:1000
+killnostable:1000000
+stabledist:0.01
+initial_perf_measuring:2
+colmask:1
+selfmask:65537
+othermask:131073
+initial_physics_active:1
+initial_self_collisions:0
+em_stat:0.0
+em_dyn:0.0
+en_assim:0.0
+
+Population:
+name:Food
+energy:1
+death:1
+initial_nn_active:0
+nn_paused:1
+perfperiod:100
+stabilperiod:100
+killnostable:1000000
+stabledist:0.01
+initial_perf_measuring:0
+colmask:1
+selfmask:131074
+othermask:65538
+initial_physics_active:1
+initial_self_collisions:0
+em_stat:0.0
+em_dyn:0.0
+en_assim:0.0
+
