Index: framspy/evolalg/dissimilarity/dissimilarity.py
===================================================================
--- framspy/evolalg/dissimilarity/dissimilarity.py	(revision 1140)
+++ framspy/evolalg/dissimilarity/dissimilarity.py	(revision 1145)
@@ -7,10 +7,11 @@
 class Dissimilarity(Step, ABC):
 
-    def __init__(self, reduction="mean", output_field="dissim", *args, **kwargs):
+    def __init__(self, reduction="mean", output_field="dissim", knn=None, *args, **kwargs):
         super(Dissimilarity, self).__init__(*args, **kwargs)
 
         self.output_field = output_field
         self.fn_reduce = None
-        if reduction == "mean":
+        self.knn = knn
+        if reduction == "mean": # TODO change this 'elif' sequence to dictionary?
             self.fn_reduce = np.mean
         elif reduction == "max":
@@ -20,8 +21,10 @@
         elif reduction == "sum":
             self.fn_reduce = np.sum
-        elif reduction == "none" or reduction == None:
+        elif reduction == "knn_mean":
+            self.fn_reduce = self.knn_mean
+        elif reduction == "none" or reduction is None:
             self.fn_reduce = None
         else:
-            raise ValueError("Unknown reduction type. Supported: mean, max, min, sum, none")
+            raise ValueError("Unknown reduction type. Supported: mean, max, min, sum, knn_mean, none")
 
     def reduce(self, dissim_matrix):
@@ -29,2 +32,5 @@
             return dissim_matrix
         return self.fn_reduce(dissim_matrix, axis=1)
+
+    def knn_mean(self, dissim_matrix,axis):
+        return np.mean(np.partition(dissim_matrix, self.knn)[:,:self.knn],axis=axis)
Index: framspy/evolalg/dissimilarity/frams_dissimilarity.py
===================================================================
--- framspy/evolalg/dissimilarity/frams_dissimilarity.py	(revision 1140)
+++ framspy/evolalg/dissimilarity/frams_dissimilarity.py	(revision 1145)
@@ -6,12 +6,15 @@
 from evolalg.dissimilarity.dissimilarity import Dissimilarity
 
+#TODO eliminate overlap with dissimilarity.py
+
 
 class FramsDissimilarity(FramsStep):
 
-    def __init__(self, frams_lib, reduction="mean", output_field="dissim", *args, **kwargs):
+    def __init__(self, frams_lib, reduction="mean", output_field="dissim", knn=None, *args, **kwargs):
         super(FramsDissimilarity, self).__init__(frams_lib, *args, **kwargs)
 
         self.output_field = output_field
         self.fn_reduce = None
+        self.knn = knn
         if reduction == "mean":
             self.fn_reduce = np.mean
@@ -22,8 +25,10 @@
         elif reduction == "sum":
             self.fn_reduce = np.sum
+        elif reduction == "knn_mean":
+            self.fn_reduce = self.knn_mean
         elif reduction == "none" or reduction is None:
             self.fn_reduce = None
         else:
-            raise ValueError("Unknown reduction type. Supported: mean, max, min, sum, none")
+            raise ValueError("Unknown reduction type. Supported: mean, max, min, sum, knn_mean, none")
 
     def reduce(self, dissim_matrix):
@@ -41,2 +46,5 @@
             setattr(ind, self.output_field, d)
         return population
+
+    def knn_mean(self, dissim_matrix,axis):
+        return np.mean(np.partition(dissim_matrix, self.knn)[:,:self.knn],axis=axis)
Index: framspy/evolalg/examples/niching_novelty.py
===================================================================
--- framspy/evolalg/examples/niching_novelty.py	(revision 1140)
+++ framspy/evolalg/examples/niching_novelty.py	(revision 1145)
@@ -23,7 +23,5 @@
 from evolalg.statistics.statistics_deap import StatisticsDeap
 from evolalg.base.union_step import UnionStep
-from evolalg.utils.name_propagation import propagate_names
 from evolalg.utils.population_save import PopulationSave
-import time
 
 
@@ -47,4 +45,6 @@
     niching = "niching"
     novelty = "novelty"
+    knn_niching = "knn_niching"
+    knn_novelty = "knn_novelty"
 
     def __str__(self):
@@ -58,5 +58,5 @@
     parser.add_argument('-path', type=ensureDir, required=True, help='Path to the Framsticks library without trailing slash.')
     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). Single or multiple criteria.')
+                        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, see multicriteria.py.')
     parser.add_argument('-lib', required=False, help="Filename of .so or .dll with the Framsticks library")
 
@@ -68,4 +68,5 @@
     parser.add_argument('-dissim', required=False, type=Dissim, default=Dissim.frams,
                         help='Dissimilarity measure, default: frams', choices=list(Dissim))
+    parser.add_argument('-knn', type=int, help="'k' value for knn-based fitness criteria (knn-niching and knn-novelty).")
     parser.add_argument('-popsize', type=int, default=50, help="Population size, default: 50.")
     parser.add_argument('-generations', type=int, default=5, help="Number of generations, default: 5.")
@@ -81,5 +82,5 @@
     parser.add_argument('-checkpoint_path', required=False, default=None, help="Path to the checkpoint file")
     parser.add_argument('-checkpoint_interval', required=False, type=int, default=100, help="Checkpoint interval")
-    parser.add_argument('--debug', dest='debug', action='store_true', help="Prints names of steps as they are executed")
+    parser.add_argument('-debug', dest='debug', action='store_true', help="Prints names of steps as they are executed")
     parser.set_defaults(debug=False)
     return parser.parse_args()
@@ -129,4 +130,5 @@
     def remove(self, individual):
         return individual.numconnections > self.max_number
+
 
 class ReplaceWithHallOfFame(Step):
@@ -138,4 +140,5 @@
         return list(self.hof.halloffame)
 
+
 def func_niching(ind): setattr(ind, "fitness", ind.fitness_raw * (1 + ind.dissim))
 
@@ -145,4 +148,10 @@
 
 def func_novelty(ind): setattr(ind, "fitness", ind.dissim)
+
+
+def func_knn_novelty(ind): setattr(ind, "fitness", ind.dissim)
+
+
+def func_knn_niching(ind): setattr(ind, "fitness", ind.fitness_raw * (1 + ind.dissim))
 
 
@@ -214,9 +223,19 @@
     # -------------------------------------------------
     # Novelty or niching
+    knn = parsed_args.knn
+    if parsed_args.fit == Fitness.knn_novelty or parsed_args.fit == Fitness.knn_niching:
+        reduction_method = "knn_mean"
+        assert knn is not None, "'k' must be set for knn-based fitness."
+        assert knn > 0, "'k' must be positive."
+        assert knn < parsed_args.popsize, "'k' must be smaller than population size."
+    else:
+        reduction_method = "mean"
+        assert knn is None, "'k' is irrelevant unless knn-based fitness is used."
+
     dissim = None
     if parsed_args.dissim == Dissim.levenshtein:
-        dissim = LevenshteinDissimilarity(reduction="mean", output_field="dissim")
+        dissim = LevenshteinDissimilarity(reduction=reduction_method, knn=knn, output_field="dissim")
     elif parsed_args.dissim == Dissim.frams:
-        dissim = FramsDissimilarity(frams_lib, reduction="mean", output_field="dissim")
+        dissim = FramsDissimilarity(frams_lib, reduction=reduction_method, knn=knn, output_field="dissim")
 
     if parsed_args.fit == Fitness.raw:
@@ -241,4 +260,20 @@
         init_stages.append(novelty)
         generation_modifications.append(novelty)
+    
+    if parsed_args.fit == Fitness.knn_niching:
+        knn_niching = UnionStep([
+            dissim,
+            LambdaStep(func_knn_niching)
+        ])
+        init_stages.append(knn_niching)
+        generation_modifications.append(knn_niching)
+    
+    if parsed_args.fit == Fitness.knn_novelty:
+        knn_novelty = UnionStep([
+            dissim,
+            LambdaStep(func_knn_novelty)
+        ])
+        init_stages.append(knn_novelty)
+        generation_modifications.append(knn_novelty)
 
     # -------------------------------------------------
