Index: framspy/FramsticksCLI.py
===================================================================
--- framspy/FramsticksCLI.py	(revision 1059)
+++ framspy/FramsticksCLI.py	(revision 1060)
@@ -7,4 +7,5 @@
 import argparse
 import numpy as np
+import framsreader  # only needed for mutation: https://pypi.org/project/framsreader
 
 
@@ -24,4 +25,5 @@
 	GENO_SAVE_FILE_FORMAT = Enum('GENO_SAVE_FILE_FORMAT', 'NATIVEFRAMS RAWGENO')  # how to save genotypes
 	OUTPUT_DIR = "scripts_output"
+	GENOTYPE_INVALID = "/*invalid*/"  # this is how genotype invalidity is represented in Framsticks
 	STDOUT_ENDOPER_MARKER = "FileObject.write"  # we look for this message on Framsticks CLI stdout to detect when Framsticks created a file with the result we expect
 
@@ -35,11 +37,11 @@
 	EVALUATE_FILE = "genos_eval.json"
 	CROSSOVER_CMD = "crossover"
-	CROSSOVER_FILE = "child.gen"
+	CROSSOVER_FILE = "crossover_child.gen"
 	DISSIMIL_CMD = "dissimil"
 	DISSIMIL_FILE = "dissimilarity_matrix.tsv"  # tab-separated values
-	ISVALID_CMD = "arevalid"
+	ISVALID_CMD = "isvalid_many"
 	ISVALID_FILE = "validity.txt"
-	MUTATE_CMD = "mutate"
-	MUTATE_FILE = "mutant.gen"
+	MUTATE_CMD = "mutate_many"
+	MUTATE_FILE = "mutation_results.gen"
 
 	CLI_INPUT_FILE = "genotypes.gen"
@@ -219,14 +221,14 @@
 
 
-	def mutate(self, genotype: str) -> str:
-		"""
-		Returns:
-			The genotype of the mutated individual. Empty string if the mutation failed.
-		"""
-		files = self.__runCommand(self.MUTATE_CMD, [genotype], self.MUTATE_FILE, self.GENO_SAVE_FILE_FORMAT["RAWGENO"])
-		with open(files[-1]) as f:
-			newgenotype = "".join(f.readlines())
-		self.__cleanUpCommandResults(files)
-		return newgenotype
+	def mutate(self, genotype_list: List[str]) -> List[str]:
+		"""
+		Returns:
+			The genotype(s) of the mutated source genotype(s). self.GENOTYPE_INVALID for genotypes whose mutation failed (for example because the source genotype was invalid).
+		"""
+		assert isinstance(genotype_list, list)  # because in python str has similar capabilities as list and here it would pretend to work too, so to avoid any ambiguity
+		files = self.__runCommand(self.MUTATE_CMD, genotype_list, self.MUTATE_FILE, self.GENO_SAVE_FILE_FORMAT["NATIVEFRAMS"])
+		genos = framsreader.load(files[-1], "gen file")
+		self.__cleanUpCommandResults(files)
+		return [g["genotype"] for g in genos]
 
 
@@ -298,10 +300,10 @@
 	# 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
+	# - "vectorize" crossover so that many genotypes is handled in one call. Even better, use .so/.dll direct communication to CLI
 	# - 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
-	# - if we read genotypes in "org:" format anywhere: import https://pypi.org/project/framsreader/0.1.2/ and use it if successful,
+	# - if we read genotypes in "org:" format anywhere: import https://pypi.org/project/framsreader and use it if successful,
 	#    if not then print a message "framsreader not available, using simple internal method to save a genotype" and proceed as it is now.
-	#    So far we don't read, but we should use the proper writer to handle all special cases like quoting etc.
+	#    We should use the proper writer to handle all special cases like quoting special characters etc.
 
 	parsed_args = parseArguments()
@@ -312,9 +314,9 @@
 	simplest = framsCLI.getSimplest('1' if parsed_args.genformat is None else parsed_args.genformat)
 	print("\tSimplest genotype:", simplest)
-	parent1 = framsCLI.mutate(simplest)
+	parent1 = framsCLI.mutate([simplest])[0]
 	parent2 = parent1
 	MUTATE_COUNT = 10
 	for x in range(MUTATE_COUNT):  # example of a chain of 20 mutations
-		parent2 = framsCLI.mutate(parent2)
+		parent2 = framsCLI.mutate([parent2])[0]
 	print("\tParent1 (mutated simplest):", parent1)
 	print("\tParent2 (Parent1 mutated %d times):" % MUTATE_COUNT, parent2)
Index: framspy/FramsticksEvolution.py
===================================================================
--- framspy/FramsticksEvolution.py	(revision 1059)
+++ framspy/FramsticksEvolution.py	(revision 1060)
@@ -14,5 +14,5 @@
 
 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.
+	genotype = individual[0]  # individual[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)
@@ -22,6 +22,6 @@
 		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)
+	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
+		fitness = [-1] * len(OPTIMIZATION_CRITERIA)  # fitness of -1 is intended to discourage further propagation of this genotype via selection ("this one is very poor")
 		print("Error '%s': could not evaluate genotype '%s', returning fitness %s" % (str(e), genotype, fitness))
 	return fitness
@@ -29,6 +29,6 @@
 
 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.
+	geno1 = individual1[0]  # individual[0] because we can't (?) have a simple str as a deap genotype/individual, only list of str.
+	geno2 = individual2[0]  # individual[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)
@@ -37,5 +37,5 @@
 
 def frams_mutate(frams_cli, individual):
-	individual[0] = frams_cli.mutate(individual[0])
+	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.
 	return individual,
 
