Index: /framspy/FramsticksLib.py
===================================================================
--- /framspy/FramsticksLib.py	(revision 1309)
+++ /framspy/FramsticksLib.py	(revision 1310)
@@ -4,4 +4,5 @@
 import sys, os
 import argparse
+import random
 import numpy as np
 import frams
@@ -19,5 +20,4 @@
 
 
-
 class FramsticksLib:
 	"""Communicates directly with Framsticks library (.dll or .so or .dylib).
@@ -36,5 +36,7 @@
 	DETERMINISTIC: bool = False  # set to True to have the same results in each run
 
-	GENOTYPE_INVALID = "/*invalid*/"  # this is how genotype invalidity is represented in Framsticks
+	GENOTYPE_INVALID = "/*invalid*/"  # this is how genotype invalidity is represented in Framsticks (Geno.format is 'invalid'). Mutation and crossover operators return such a genotype if they were unable to perform their operation (information about the cause is stored in the Geno.info field - see GenMan.cpp)
+	GENOTYPE_INVALID_OFFSPRING_SUBSTITUTE_ORIGINAL = True  # if True, when mutation or crossover is unable to perform their operation for the provided genotype(s), return the original genotype (and print a warning). If this happens extremely rarely, it may be ignored - but if not, you need to identify the reason (e.g., particular genotypes that cause the problem), fix it or change the logic of your algorithm. A more strict approach is to keep this field False - then you must always check if GENOTYPE_INVALID was returned by mutate() or crossOver(), and handle this situation properly (e.g., choose different parent(s) for mutate() or crossOver() and repeat until you get a valid offspring).
+
 	EVALUATION_SETTINGS_FILE = [  # all files MUST be compatible with the standard-eval expdef. The order they are loaded in is important!
 		"eval-allcriteria.sim",  # a good trade-off in performance sampling period ("perfperiod") for vertpos and velocity
@@ -88,4 +90,12 @@
 
 
+	@staticmethod
+	def shortGenotype(genotype: str) -> str:
+		"""
+		Returns a few initial characters of the genotype, just for information/identifying the genotype.
+		"""
+		return repr(genotype) if len(genotype) <= 10 else repr(genotype[:10] + "...")
+
+
 	def getSimplest(self, genetic_format: str) -> str:
 		return frams.GenMan.getSimplest(genetic_format).genotype._string()
@@ -168,5 +178,5 @@
 			ec.close()
 			if ec.error_count._value() > 0:
-				print('\nErrors while evaluating this genotype list:\n',genotype_list,sep='\t')
+				print('\nErrors while evaluating this genotype list:\n', genotype_list, sep='\t')
 				print(ec.messages)  # if errors occurred, output all caught messages for debugging
 				raise RuntimeError("[ERROR] %d error(s) and %d warning(s) while evaluating %d genotype(s)" % (ec.error_count._value(), ec.warning_count._value(), len(genotype_list)))  # make errors fatal; by default they stop the simulation anyway so let's not use potentially incorrect or partial results and fix the cause first.
@@ -191,6 +201,11 @@
 
 		mutated = []
-		for g in genotype_list:
-			mutated.append(frams.GenMan.mutate(frams.Geno.newFromString(g)).genotype._string())
+		for genotype_parent in genotype_list:
+			offspring = frams.GenMan.mutate(frams.Geno.newFromString(genotype_parent))
+			offspring_genotype = offspring.genotype._string()
+			if offspring_genotype == self.GENOTYPE_INVALID and self.GENOTYPE_INVALID_OFFSPRING_SUBSTITUTE_ORIGINAL:
+				print('[WARN] mutate(%s) failed but you requested GENOTYPE_INVALID_OFFSPRING_SUBSTITUTE_ORIGINAL, so returning the original genotype instead. Reason for failure: %s' % (self.shortGenotype(genotype_parent), offspring.info._string()))
+				offspring_genotype = genotype_parent
+			mutated.append(offspring_genotype)
 		if len(genotype_list) != len(mutated):
 			raise RuntimeError("Submitted %d genotypes, received %d mutants" % (len(genotype_list), len(mutated)))
@@ -203,5 +218,10 @@
 			The genotype of the offspring. self.GENOTYPE_INVALID if the crossing over failed.
 		"""
-		return frams.GenMan.crossOver(frams.Geno.newFromString(genotype_parent1), frams.Geno.newFromString(genotype_parent2)).genotype._string()
+		offspring = frams.GenMan.crossOver(frams.Geno.newFromString(genotype_parent1), frams.Geno.newFromString(genotype_parent2))
+		offspring_genotype = offspring.genotype._string()
+		if offspring_genotype == self.GENOTYPE_INVALID and self.GENOTYPE_INVALID_OFFSPRING_SUBSTITUTE_ORIGINAL:
+			print('[WARN] crossOver(%s, %s) failed but you requested GENOTYPE_INVALID_OFFSPRING_SUBSTITUTE_ORIGINAL, so returning a random parent instead. Reason for failure: %s' % (self.shortGenotype(genotype_parent1), self.shortGenotype(genotype_parent2), offspring.info._string()))
+			offspring_genotype = random.choice([genotype_parent1, genotype_parent2])
+		return offspring_genotype
 
 
