Index: /framspy/evolalg/base/frams_step.py
===================================================================
--- /framspy/evolalg/base/frams_step.py	(revision 1113)
+++ /framspy/evolalg/base/frams_step.py	(revision 1113)
@@ -0,0 +1,13 @@
+from evolalg.base.step import Step
+
+
+class FramsStep(Step):
+    def __init__(self, frams_lib, commands=None):
+        if commands is None:
+            commands = []
+        self.frams = frams_lib
+        self.commands = commands
+
+    def pre(self):
+        for c in self.commands:
+            self.frams.sendDirectCommand(c) #TODO update to use FramsticksLib when needed, pass lambda, import frams module? Maybe pre-lambda returns old value, post-lambda restores what pre-lambda returned. Or pass ExtValue or list of ExtValues and their new values (and either caller saves original values to be restored, or pre() returns a list of original values to be restored in post())
Index: /framspy/evolalg/base/individual.py
===================================================================
--- /framspy/evolalg/base/individual.py	(revision 1113)
+++ /framspy/evolalg/base/individual.py	(revision 1113)
@@ -0,0 +1,5 @@
+
+
+class Individual:
+    def __init__(self, genotype):
+        self.genotype = genotype
Index: /framspy/evolalg/base/lambda_step.py
===================================================================
--- /framspy/evolalg/base/lambda_step.py	(revision 1113)
+++ /framspy/evolalg/base/lambda_step.py	(revision 1113)
@@ -0,0 +1,24 @@
+from evolalg.base.step import Step
+
+
+class LambdaStep(Step):
+    """
+    Wrapper for lambda expressions. In "normal" mode each step is given whole population to work with. This class
+    helps with any Callable that accepts individuals.
+
+    """
+    def __init__(self, fun, in_place=True, *args, **kwargs):
+        """
+        @param fun: Callable callable that will be called for each individual
+        @param in_place: Bool
+        """
+        super(LambdaStep, self).__init__(*args, **kwargs)
+        self.fun = fun
+        self.in_place = in_place
+
+    def call(self, population):
+        if self.in_place:
+            [self.fun(_) for _ in population]
+        else:
+            population = [self.fun(_) for _ in population]
+        return population
Index: /framspy/evolalg/base/step.py
===================================================================
--- /framspy/evolalg/base/step.py	(revision 1113)
+++ /framspy/evolalg/base/step.py	(revision 1113)
@@ -0,0 +1,26 @@
+from abc import abstractmethod
+
+
+class Step:
+    """
+    Base abstract class for experiment's steps. It has three stages: pre, call and post.
+
+    """
+    def pre(self):
+        pass
+
+    @abstractmethod
+    def call(self, *args, **kwargs):
+        pass
+
+    def post(self):
+        pass
+
+    def init(self):
+        pass
+
+    def __call__(self, *args, **kwargs):
+        self.pre()
+        res = self.call(*args, **kwargs)
+        self.post()
+        return res
Index: /framspy/evolalg/base/union_step.py
===================================================================
--- /framspy/evolalg/base/union_step.py	(revision 1113)
+++ /framspy/evolalg/base/union_step.py	(revision 1113)
@@ -0,0 +1,16 @@
+from collections import Iterable
+
+from evolalg.base.step import Step
+
+
+class UnionStep(Step):
+    def __init__(self, steps):
+        if isinstance(steps, Iterable):
+            self.steps = steps
+        else:
+            self.steps = [steps]
+
+    def call(self, population):
+        for s in self.steps:
+            population = s(population)
+        return population
Index: /framspy/evolalg/dissimilarity/dissimilarity.py
===================================================================
--- /framspy/evolalg/dissimilarity/dissimilarity.py	(revision 1113)
+++ /framspy/evolalg/dissimilarity/dissimilarity.py	(revision 1113)
@@ -0,0 +1,30 @@
+from abc import ABC
+
+from evolalg.base.step import Step
+import numpy as np
+
+
+class Dissimilarity(Step, ABC):
+
+    def __init__(self, reduction="mean", output_field="dissim", *args, **kwargs):
+        super(Dissimilarity, self).__init__(*args, **kwargs)
+
+        self.output_field = output_field
+        self.fn_reduce = None
+        if reduction == "mean":
+            self.fn_reduce = np.mean
+        elif reduction == "max":
+            self.fn_reduce = np.max
+        elif reduction == "min":
+            self.fn_reduce = np.min
+        elif reduction == "sum":
+            self.fn_reduce = np.sum
+        elif reduction == "none" or reduction == None:
+            self.fn_reduce = None
+        else:
+            raise ValueError("Unknown reduction type. Supported: mean, max, min, sum, none")
+
+    def reduce(self, dissim_matrix):
+        if self.fn_reduce is None:
+            return dissim_matrix
+        return self.fn_reduce(dissim_matrix, axis=1)
Index: /framspy/evolalg/dissimilarity/frams_dissimilarity.py
===================================================================
--- /framspy/evolalg/dissimilarity/frams_dissimilarity.py	(revision 1113)
+++ /framspy/evolalg/dissimilarity/frams_dissimilarity.py	(revision 1113)
@@ -0,0 +1,41 @@
+from abc import ABC
+
+import numpy as np
+
+from evolalg.base.frams_step import FramsStep
+from evolalg.dissimilarity.dissimilarity import Dissimilarity
+
+
+class FramsDissimilarity(FramsStep):
+
+    def __init__(self, frams_lib, reduction="mean", output_field="dissim", *args, **kwargs):
+        super(FramsDissimilarity, self).__init__(frams_lib, *args, **kwargs)
+
+        self.output_field = output_field
+        self.fn_reduce = None
+        if reduction == "mean":
+            self.fn_reduce = np.mean
+        elif reduction == "max":
+            self.fn_reduce = np.max
+        elif reduction == "min":
+            self.fn_reduce = np.min
+        elif reduction == "sum":
+            self.fn_reduce = np.sum
+        elif reduction == "none" or reduction is None:
+            self.fn_reduce = None
+        else:
+            raise ValueError("Unknown reduction type. Supported: mean, max, min, sum, none")
+
+    def reduce(self, dissim_matrix):
+        if self.fn_reduce is None:
+            return dissim_matrix
+        return self.fn_reduce(dissim_matrix, axis=1)
+
+    def call(self, population):
+        if len(population) == 0:
+            return []
+        dissim_matrix = self.frams.dissimilarity([_.genotype for _ in population])
+        dissim = self.reduce(dissim_matrix)
+        for d,ind in zip(dissim, population):
+            setattr(ind, self.output_field, d)
+        return population
Index: /framspy/evolalg/dissimilarity/levenshtein.py
===================================================================
--- /framspy/evolalg/dissimilarity/levenshtein.py	(revision 1113)
+++ /framspy/evolalg/dissimilarity/levenshtein.py	(revision 1113)
@@ -0,0 +1,22 @@
+import Levenshtein as lev
+
+from evolalg.dissimilarity.dissimilarity import Dissimilarity
+
+
+class LevenshteinDissimilarity(Dissimilarity):
+    def __init__(self, reduction="mean", output_field="dissim", *args, **kwargs):
+        super(LevenshteinDissimilarity, self).__init__(reduction, output_field, *args, **kwargs)
+
+    def call(self, population):
+        if len(population) == 0:
+            return []
+        dissim = []
+        for i, p in enumerate(population):
+            gen_dis = []
+            for i2, p2 in enumerate(population):
+                gen_dis.append(lev.distance(p.genotype, p2.genotype))
+            dissim.append(gen_dis)
+        dissim = self.reduce(dissim)
+        for d, ind in zip(dissim, population):
+            setattr(ind, self.output_field, d)
+        return population
Index: /framspy/evolalg/examples/invalid.py
===================================================================
--- /framspy/evolalg/examples/invalid.py	(revision 1113)
+++ /framspy/evolalg/examples/invalid.py	(revision 1113)
@@ -0,0 +1,114 @@
+import random
+
+import argparse
+import os
+import sys
+import numpy as np
+
+from evolalg.base.lambda_step import LambdaStep
+from evolalg.experiment import Experiment
+from evolalg.fitness.fitness_step import FitnessStep
+from evolalg.mutation_cross.frams_cross_and_mutate import FramsCrossAndMutate
+from evolalg.population.frams_population import FramsPopulation
+from evolalg.repair.multistep import MultistepRepair
+from evolalg.repair.remove.field import FieldRemove
+from evolalg.selection.tournament import TournamentSelection
+from evolalg.statistics.halloffame_stats import HallOfFameStatistics
+from evolalg.statistics.statistics_deap import StatisticsDeap
+from evolalg.base.union_step import UnionStep
+from evolalg.utils.population_save import PopulationSave
+from evolalg.utils.stable_generation import StableGeneration
+from FramsticksLib import FramsticksLib
+
+
+def ensureDir(string):
+    if os.path.isdir(string):
+        return string
+    else:
+        raise NotADirectoryError(string)
+
+
+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 without trailing slash.')
+    # parser.add_argument('-opt', required=True,
+    #                     help='optimization criteria : vertpos, velocity, distance, vertvel, lifespan, numjoints, numparts, numneurons, numconnections. Single or multiple criteria.')
+    parser.add_argument('-lib', required=False, help="Filename of .so or .dll with framsticks library")
+    parser.add_argument('-genformat', required=False, default="1",
+                        help='Genetic format for the demo run, for example 4, 9, or B. If not given, f1 is assumed.')
+
+    parser.add_argument("-popsize", type=int, default=50, help="Size of population, default 50.")
+    return parser.parse_args()
+
+
+def extract_fitness(ind):
+    return ind.fitness
+
+
+def print_population_count(pop):
+    print("Current:",len(pop))
+    return pop # Each step must return a population
+
+
+def random_remove(pop):
+    return [_ for _ in pop if random.randint(0, 1) == 0]
+
+
+def main():
+    parsed_args = parseArguments()
+    frams_lib = FramsticksLib(parsed_args.path, parsed_args.lib, "eval-movement.sim")
+
+    hall_of_fame = HallOfFameStatistics(100, "fitness")
+    statistics_union = UnionStep([
+        hall_of_fame,
+        StatisticsDeap([
+            ("avg", np.mean),
+            ("stddev", np.std),
+            ("min", np.min),
+            ("max", np.max),
+            ("count", len)
+        ], extract_fitness)
+    ])
+
+    fitness_remove = UnionStep([
+        FitnessStep(frams_lib, fields={"velocity": "fitness", "data->recording": "recording"},
+                    fields_defaults={"velocity": None, "data->recording": None}),
+        FieldRemove("recording", None),
+        print_population_count,  # Stages can be also any Callable
+    ])
+
+    selection = TournamentSelection(5, copy=True)
+    new_generation_steps = [
+        FramsCrossAndMutate(frams_lib, cross_prob=0.2, mutate_prob=0.9),
+        fitness_remove
+    ]
+
+    generation_modifications = [
+        statistics_union  # Or niching, novelty
+    ]
+
+    init_stages = [FramsPopulation(frams_lib, parsed_args.genformat, parsed_args.popsize),
+                   fitness_remove,  # It is possible to create smaller population
+                   statistics_union]
+
+    end_steps = [PopulationSave("halloffame.gen", provider=hall_of_fame.haloffame, fields={"genotype": "genotype",
+                                                                                           "fitness": "fitness",
+                                                                                           "custom": "recording"})]
+
+    experiment = Experiment(init_population=init_stages,
+                            selection=selection,
+                            new_generation_steps=new_generation_steps,
+                            generation_modification=generation_modifications,
+                            end_steps=end_steps,
+                            population_size=parsed_args.popsize
+                            )
+    experiment.init()
+    experiment.run(3)
+    for ind in hall_of_fame.haloffame:
+        print("%g\t%s" % (ind.fitness, ind.genotype))
+
+
+if __name__ == '__main__':
+    main()
Index: /framspy/evolalg/examples/niching_novelty.py
===================================================================
--- /framspy/evolalg/examples/niching_novelty.py	(revision 1113)
+++ /framspy/evolalg/examples/niching_novelty.py	(revision 1113)
@@ -0,0 +1,233 @@
+import argparse
+import os
+import sys
+from enum import Enum
+
+import numpy as np
+
+from FramsticksLib import FramsticksLib
+from evolalg.base.lambda_step import LambdaStep
+from evolalg.dissimilarity.frams_dissimilarity import FramsDissimilarity
+from evolalg.dissimilarity.levenshtein import LevenshteinDissimilarity
+from evolalg.experiment import Experiment
+from evolalg.fitness.fitness_step import FitnessStep
+from evolalg.mutation_cross.frams_cross_and_mutate import FramsCrossAndMutate
+from evolalg.population.frams_population import FramsPopulation
+from evolalg.repair.remove.field import FieldRemove
+from evolalg.repair.remove.remove import Remove
+from evolalg.selection.tournament import TournamentSelection
+from evolalg.statistics.halloffame_stats import HallOfFameStatistics
+from evolalg.statistics.statistics_deap import StatisticsDeap
+from evolalg.base.union_step import UnionStep
+from evolalg.utils.population_save import PopulationSave
+
+
+def ensureDir(string):
+    if os.path.isdir(string):
+        return string
+    else:
+        raise NotADirectoryError(string)
+
+
+class Dissim(Enum):
+    levenshtein = "levenshtein"
+    frams = "frams"
+
+    def __str__(self):
+        return self.name
+
+
+class Fitness(Enum):
+    raw = "raw"
+    niching = "niching"
+    novelty = "novelty"
+
+    def __str__(self):
+        return self.name
+
+
+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 without trailing slash.')
+    parser.add_argument('-opt', required=True,
+                        help='optimization criteria : vertpos, velocity, distance, vertvel, lifespan, numjoints, numparts, numneurons, numconnections. Single or multiple criteria.')
+    parser.add_argument('-lib', required=False, help="Filename of .so or .dll with framsticks library")
+    parser.add_argument('-genformat', required=False, default="1",
+                        help='Genetic format for the demo run, for example 4, 9, or B. If not given, f1 is assumed.')
+    parser.add_argument('-sim', required=False, default="eval-allcriteria.sim", help="Name of .sim file")
+    parser.add_argument('-dissim', required=False, type=Dissim, default=Dissim.frams,
+                        help=' Dissimilarity measure DEFAULT = frams', choices=list(Dissim))
+    parser.add_argument('-fit', required=False, default=Fitness.raw, type=Fitness,
+                        help=' Fitness criteria DEFAULT = raw', choices=list(Fitness))
+    parser.add_argument('-popsize', type=int, default=50, help="Size of population, default 50.")
+    parser.add_argument('-num_parts', type=int, default=None, help="Maximum number of parts. Default None")
+    parser.add_argument('-checkpoint_path', required=False, default=None, help="Path to checkpoint path")
+    parser.add_argument('-checkpoint_interval', required=False, type=int, default=100, help="Checkpoint interval")
+    return parser.parse_args()
+
+
+def extract_fitness(ind):
+    return ind.fitness_raw
+
+
+def print_population_count(pop):
+    print("Current:", len(pop))
+    return pop  # Each step must return a population
+
+
+class NumPartsGreater(Remove):
+    def __init__(self, numparts):
+        super(NumPartsGreater, self).__init__()
+        self.numparts = numparts
+
+    def remove(self, individual):
+        return individual.numparts > self.numparts
+
+
+def func_niching(ind): setattr(ind, "fitness", ind.fitness_raw * (1 + ind.dissim))
+
+
+def func_raw(ind): setattr(ind, "fitness", ind.fitness_raw)
+
+
+def func_novelty(ind): setattr(ind, "fitness", ind.dissim)
+
+
+def main():
+    print("Running experiment with", sys.argv)
+    parsed_args = parseArguments()
+    frams = FramsticksLib(parsed_args.path, parsed_args.lib,
+                          parsed_args.sim)
+    # Steps for generating first population
+    init_stages = [FramsPopulation(frams, parsed_args.genformat, parsed_args.popsize)]
+
+    # Selection procedure
+    selection = TournamentSelection(5, copy=True) # 'fitness' by default, the targeted attribute can be changed, e.g. fit_attr="fitness_raw"
+
+    # Procedure for generating new population. This steps will be run as long there is less than
+    # popsize individuals in the new population
+    new_generation_stages = [FramsCrossAndMutate(frams, cross_prob=0.2, mutate_prob=0.9)]
+
+    # Steps after new population is created. Executed exacly once per generation.
+    generation_modifications = []
+
+    # -------------------------------------------------
+    # Fitness
+
+    fitness_raw = FitnessStep(frams, fields={parsed_args.opt: "fitness_raw", "numparts": "numparts"},
+                              fields_defaults={parsed_args.opt: None, "numparts": float("inf")},
+                              evaluation_count=1)
+
+    fitness_end = FitnessStep(frams, fields={parsed_args.opt: "fitness_raw"},
+                              fields_defaults={parsed_args.opt: None},
+                              evaluation_count=100)  # evaluate the contents of the last population 100 times (TODO replace this approach and evaluate HOF instead of the last population)
+    # Remove
+    remove = []
+    remove.append(FieldRemove("fitness_raw", None))  # Remove individuals if they have default value for fitness
+    if parsed_args.num_parts is not None:
+        # This could be also implemented by "LambdaRemove(lambda x: x.numparts > parsed_args.num_parts)
+        # But this will not serialize in checkpoint.
+        remove.append(NumPartsGreater(parsed_args.num_parts))
+    remove_step = UnionStep(remove)
+
+    fitness_remove = UnionStep([fitness_raw, remove_step])
+
+    init_stages.append(fitness_remove)
+    new_generation_stages.append(fitness_remove)
+
+    # -------------------------------------------------
+    # Novelty or niching
+    dissim = None
+    if parsed_args.dissim == Dissim.levenshtein:
+        dissim = LevenshteinDissimilarity(reduction="mean", output_field="dissim")
+    elif parsed_args.dissim == Dissim.frams:
+        dissim = FramsDissimilarity(frams, reduction="mean", output_field="dissim")
+
+    if parsed_args.fit == Fitness.raw:
+        # Fitness is equal to finess raw
+        raw = LambdaStep(func_raw)
+        init_stages.append(raw)
+        new_generation_stages.append(raw)
+        generation_modifications.append(raw)
+
+    if parsed_args.fit == Fitness.niching:
+        niching = UnionStep([
+            dissim,
+            LambdaStep(func_niching)
+        ])
+        init_stages.append(niching)
+        new_generation_stages.append(niching)
+        generation_modifications.append(niching)
+
+    if parsed_args.fit == Fitness.novelty:
+        novelty = UnionStep([
+            dissim,
+            LambdaStep(func_novelty)
+        ])
+        init_stages.append(novelty)
+        new_generation_stages.append(novelty)
+        generation_modifications.append(novelty)
+
+    # -------------------------------------------------
+    # Statistics
+    hall_of_fame = HallOfFameStatistics(100, "fitness_raw")  # Wrapper for halloffamae
+    statistics_deap = StatisticsDeap([
+        ("avg", np.mean),
+        ("stddev", np.std),
+        ("min", np.min),
+        ("max", np.max)
+    ], extract_fitness)  # Wrapper for deap statistics
+
+    statistics_union = UnionStep([
+        hall_of_fame,
+        statistics_deap
+    ])  # Union of two statistics steps.
+
+    init_stages.append(statistics_union)
+    generation_modifications.append(statistics_union)
+
+    # -------------------------------------------------
+    # End stages: this will execute exacly once after all generations.
+    end_stages = [
+        fitness_end,
+        PopulationSave("halloffame.gen", provider=hall_of_fame.haloffame, fields={"genotype": "genotype",
+                                                                                            "fitness": "fitness_raw"})]
+    # ...but custom fields can be added, e.g. "custom": "recording"
+
+    # -------------------------------------------------
+    # Experiment creation
+
+    experiment = Experiment(init_population=init_stages,
+                            selection=selection,
+                            new_generation_steps=new_generation_stages,
+                            generation_modification=generation_modifications,
+                            end_steps=end_stages,
+                            population_size=parsed_args.popsize,
+                            checkpoint_path=parsed_args.checkpoint_path,
+                            checkpoint_interval=parsed_args.checkpoint_interval
+                            )
+    experiment.init()  # init is mandatory
+    experiment.run(10)
+    # Next call for experiment.run(10) will do nothing. Parameter 10 specifies how many generations should be
+    # in one experiment. Previous call generated 10 generations.
+    # Example 1:
+    # experiment.init()
+    # experiment.run(10)
+    # experiment.run(12)
+    # #This will run for total of 12 generations
+    #
+    # Example 2
+    # experiment.init()
+    # experiment.run(10)
+    # experiment.init()
+    # experiment.run(10)
+    # # All work produced by first run will be "destroyed" by second init().
+
+    for ind in hall_of_fame.haloffame:
+        print("%g\t%s" % (ind.fitness, ind.genotype))
+
+
+if __name__ == '__main__':
+    main()
Index: /framspy/evolalg/examples/standard.py
===================================================================
--- /framspy/evolalg/examples/standard.py	(revision 1113)
+++ /framspy/evolalg/examples/standard.py	(revision 1113)
@@ -0,0 +1,104 @@
+import argparse
+import os
+import sys
+import numpy as np
+
+#TODO add new example: steadystate.py (analogous to standard.py)
+#TODO extend both standard.py and steadystate.py to support >1 criteria (using DEAP's selNSGA2() and selSPEA2())
+#TODO add comments to all examples in this directory
+#TODO add to standard.py and steadystate.py evaluating each genotype in HOF N (configurable, default 10) times when the evolution ends
+#TODO protect all examples against invalid genotypes (fill population until all genotypes are conrrectly evaluated). And maybe remove invalid.py if it overlaps with (is a subset of) other examples
+
+from FramsticksLib import FramsticksLib
+from evolalg.base.lambda_step import LambdaStep
+from evolalg.dissimilarity.frams_dissimilarity import FramsDissimilarity
+from evolalg.experiment import Experiment
+from evolalg.fitness.fitness_step import FitnessStep
+from evolalg.mutation_cross.frams_cross_and_mutate import FramsCrossAndMutate
+from evolalg.population.frams_population import FramsPopulation
+from evolalg.selection.tournament import TournamentSelection
+from evolalg.statistics.halloffame_stats import HallOfFameStatistics
+from evolalg.statistics.statistics_deap import StatisticsDeap
+from evolalg.base.union_step import UnionStep
+
+
+def ensureDir(string):
+    if os.path.isdir(string):
+        return string
+    else:
+        raise NotADirectoryError(string)
+
+
+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 without trailing slash.')
+    parser.add_argument('-opt', required=True,
+                        help='optimization criteria : vertpos, velocity, distance, vertvel, lifespan, numjoints, numparts, numneurons, numconnections. Single or multiple criteria.')
+    parser.add_argument('-lib', required=False, help="Filename of .so or .dll with framsticks library")
+    parser.add_argument('-genformat', required=False, default="1",
+                        help='Genetic format for the demo run, for example 4, 9, or B. If not given, f1 is assumed.')
+
+    parser.add_argument("-popsize", type=int, default=50, help="Size of population, default 50.")
+    return parser.parse_args()
+
+
+def extract_fitness(ind):
+    return ind.fitness
+
+
+def print_population_count(pop):
+    print("Current:", len(pop))
+    return pop  # Each step must return a population
+
+
+def main():
+    parsed_args = parseArguments()
+    frams = FramsticksLib(parsed_args.path, parsed_args.lib,
+                          "eval-allcriteria.sim")
+
+    hall_of_fame = HallOfFameStatistics(100, "fitness")
+    statistics_union = UnionStep([
+        hall_of_fame,
+        StatisticsDeap([
+            ("avg", np.mean),
+            ("stddev", np.std),
+            ("min", np.min),
+            ("max", np.max),
+            ("count", len)
+        ], extract_fitness)
+    ])
+
+    fitness = FitnessStep(frams, fields={parsed_args.opt: "fitness", }, fields_defaults={})
+
+    init_stages = [FramsPopulation(frams, parsed_args.genformat, 50),
+                   fitness,
+                   statistics_union]
+
+    selection = TournamentSelection(5, copy=True, fit_attr="fitness")
+
+    new_generation_steps = [
+        FramsCrossAndMutate(frams, cross_prob=0.2, mutate_prob=0.9),
+        fitness,
+    ]
+
+    generation_modifications = [
+        statistics_union
+    ]
+
+    experiment = Experiment(init_population=init_stages,
+                            selection=selection,
+                            new_generation_steps=new_generation_steps,
+                            generation_modification=generation_modifications,
+                            end_steps=[],
+                            population_size=parsed_args.popsize
+                            )
+    experiment.init()
+    experiment.run(3)
+    for ind in hall_of_fame.haloffame:
+        print("%g\t%s" % (ind.fitness, ind.genotype))
+
+
+if __name__ == '__main__':
+    main()
Index: /framspy/evolalg/experiment.py
===================================================================
--- /framspy/evolalg/experiment.py	(revision 1113)
+++ /framspy/evolalg/experiment.py	(revision 1113)
@@ -0,0 +1,65 @@
+from typing import List, Callable, Union
+
+from evolalg.base.step import Step
+import pickle
+
+from evolalg.base.union_step import UnionStep
+from evolalg.selection.selection import Selection
+from evolalg.utils.stable_generation import StableGeneration
+
+
+class Experiment:
+    def __init__(self, init_population: List[Callable],
+                 selection: Selection,
+                 new_generation_steps: List[Union[Callable, Step]],
+                 generation_modification: List[Union[Callable, Step]],
+                 end_steps: List[Union[Callable, Step]],
+                 population_size,
+                 checkpoint_path=None, checkpoint_interval=None):
+
+        self.init_population = init_population
+        self.step = StableGeneration(
+            selection=selection,
+            steps=new_generation_steps,
+            population_size=population_size)
+        self.generation_modification = UnionStep(generation_modification)
+
+        self.end_steps = UnionStep(end_steps)
+
+        self.checkpoint_path = checkpoint_path
+        self.checkpoint_interval = checkpoint_interval
+        self.generation = 0
+        self.population = None
+
+    def init(self):
+        self.generation = 0
+        for s in self.init_population:
+            if isinstance(s, Step):
+                s.init()
+
+        self.step.init()
+        self.generation_modification.init()
+        self.end_steps.init()
+
+        for s in self.init_population:
+            self.population = s(self.population)
+
+    def run(self, num_generations):
+        for i in range(self.generation + 1, num_generations + 1):
+            self.generation = i
+            self.population = self.step(self.population)
+            self.population = self.generation_modification(self.population)
+
+            if (self.checkpoint_path is not None
+                    and self.checkpoint_interval is not None
+                    and i % self.checkpoint_interval == 0):
+                with open(self.checkpoint_path, "wb") as file:
+                    pickle.dump(self, file)
+
+        self.population = self.end_steps(self.population)
+
+    @staticmethod
+    def restore(path):
+        with open(path) as file:
+            res = pickle.load(file)
+        return res
Index: /framspy/evolalg/fitness/fitness_step.py
===================================================================
--- /framspy/evolalg/fitness/fitness_step.py	(revision 1113)
+++ /framspy/evolalg/fitness/fitness_step.py	(revision 1113)
@@ -0,0 +1,41 @@
+from typing import Dict, List
+
+from evolalg.base.frams_step import FramsStep
+from evolalg.base.individual import Individual
+import frams
+
+
+class FitnessStep(FramsStep):
+    def __init__(self, frams_lib, fields: Dict, fields_defaults: Dict, commands: List[str] = None,
+                 vectorized: bool = True, evaluation_count=None):
+
+        super().__init__(frams_lib, commands)
+        self.fields = fields
+        self.fields_defaults = fields_defaults
+        self.vectorized = vectorized
+        self.evaluation_count = evaluation_count
+        self.evaluation_count_original = None  # to be able to restore to original value after it is changed
+
+    def pre(self):
+        if self.evaluation_count is not None:
+            self.evaluation_count_original = frams.ExpProperties.evalcount._value()  # store original value and restore it in post()
+            frams.ExpProperties.evalcount = self.evaluation_count
+
+    def post(self):
+        if self.evaluation_count is not None:
+            frams.ExpProperties.evalcount = self.evaluation_count_original
+            self.evaluation_count_original = None
+
+    def call(self, population: List[Individual]):
+        if self.vectorized:
+            data = self.frams.evaluate([_.genotype for _ in population])
+        else:
+            data = [self.frams.evaluate([_.genotype]) for _ in population]
+
+        for ind, d in zip(population, data):
+            for k, v in self.fields.items():
+                try:
+                    setattr(ind, v, d["evaluations"][""][k])
+                except:
+                    setattr(ind, v, self.fields_defaults[k])
+        return population
Index: /framspy/evolalg/fitness/multiple_evaluations.py
===================================================================
--- /framspy/evolalg/fitness/multiple_evaluations.py	(revision 1113)
+++ /framspy/evolalg/fitness/multiple_evaluations.py	(revision 1113)
@@ -0,0 +1,19 @@
+from typing import Dict, List
+
+from evolalg.base.individual import Individual
+from evolalg.fitness.fitness_step import FitnessStep
+import copy
+
+class MultipleEvaluationFitness(FitnessStep):
+    def __init__(self, frams_lib, fields: Dict, fields_defaults: Dict, commands: List[str] = None,
+                 vectorized: bool = True, number_evaluations = 10):
+        super().__init__(frams_lib, fields, fields_defaults, commands, vectorized)
+        self.number_evaluations = number_evaluations
+
+
+    def call(self, population: List[Individual]):
+        pop = copy.deepcopy(population)
+        statistics = [{} for _ in range(len(population))]
+        for _ in self.number_evaluations:
+            pass
+        return population
Index: /framspy/evolalg/mutation_cross/frams_cross.py
===================================================================
--- /framspy/evolalg/mutation_cross/frams_cross.py	(revision 1113)
+++ /framspy/evolalg/mutation_cross/frams_cross.py	(revision 1113)
@@ -0,0 +1,24 @@
+import random
+
+from evolalg.base.frams_step import FramsStep
+from evolalg.base.individual import Individual
+
+
+class FramsCross(FramsStep):
+    def __init__(self, frams_lib, commands, cross_prob):
+        super().__init__(frams_lib, commands)
+        self.cross_prob = cross_prob
+
+    def call(self, population):
+        for i in range(1, len(population), 2):
+            if random.random() < self.cross_prob:
+                geno1 = population[i - 1].genotype
+                geno2 = population[i].genotype
+
+                cross_geno1 = self.frams.crossOver(geno1, geno2)
+                cross_geno2 = self.frams.crossOver(geno1, geno2)
+
+                population[i - 1] = Individual(cross_geno1)
+                population[i] = Individual(cross_geno2)
+
+        return population
Index: /framspy/evolalg/mutation_cross/frams_cross_and_mutate.py
===================================================================
--- /framspy/evolalg/mutation_cross/frams_cross_and_mutate.py	(revision 1113)
+++ /framspy/evolalg/mutation_cross/frams_cross_and_mutate.py	(revision 1113)
@@ -0,0 +1,19 @@
+from evolalg.base.frams_step import FramsStep
+from evolalg.mutation_cross.frams_cross import FramsCross
+from evolalg.mutation_cross.frams_mutation import FramsMutation
+
+
+class FramsCrossAndMutate(FramsStep):
+    def __init__(self, frams_lib, cross_prob, mutate_prob, mutate_commands=None, cross_commands=None):
+        super().__init__(frams_lib)
+
+        self.cross_prob = cross_prob
+        self.mutate_prob = mutate_prob
+
+        self.frams_mutate = FramsMutation(frams_lib, mutate_commands, mutate_prob)
+        self.frams_cross = FramsCross(frams_lib, cross_commands, cross_prob)
+
+    def call(self, population):
+        population = self.frams_cross(population)
+        population = self.frams_mutate(population)
+        return population
Index: /framspy/evolalg/mutation_cross/frams_cross_or_mutate.py
===================================================================
--- /framspy/evolalg/mutation_cross/frams_cross_or_mutate.py	(revision 1113)
+++ /framspy/evolalg/mutation_cross/frams_cross_or_mutate.py	(revision 1113)
@@ -0,0 +1,40 @@
+from evolalg.base.frams_step import FramsStep
+from evolalg.mutation_cross.frams_cross import FramsCross
+from evolalg.mutation_cross.frams_mutation import FramsMutation
+import random
+
+class FramsCrossOrMutate(FramsStep):
+    def __init__(self, frams_lib, commands, cross_prob, mutate_prob, mutate_commands=None, cross_commands=None):
+        super().__init__(frams_lib, commands)
+
+        self.cross_prob = cross_prob
+        self.mutate_prob = mutate_prob
+
+        self.frams_mutate = FramsMutation(frams_lib, mutate_commands, 1)
+        self.frams_cross = FramsCross(frams_lib, cross_commands, 1)
+
+    def call(self, population):
+        mutate_idx = []
+        cross_idx = []
+        for i in range(len(population)):
+            rng = random.random()
+            if rng < self.cross_prob:
+                cross_idx.append(i)
+            elif rng < self.cross_prob+self.mutate_prob:
+                mutate_idx.append(i)
+
+        mutate_ind = [population[_] for _ in mutate_idx]
+        cross_ind = [population[_] for _ in cross_idx]
+
+        if len(mutate_ind) > 0:
+            self.frams_mutate(mutate_ind)
+        if len(cross_idx) > 0:
+            self.frams_cross(cross_idx)
+
+        for i,ind in zip(mutate_idx, mutate_ind):
+            population[i] = ind
+
+        for i,ind in zip(cross_idx, cross_ind):
+            population[i] = ind
+
+        return population
Index: /framspy/evolalg/mutation_cross/frams_mutation.py
===================================================================
--- /framspy/evolalg/mutation_cross/frams_mutation.py	(revision 1113)
+++ /framspy/evolalg/mutation_cross/frams_mutation.py	(revision 1113)
@@ -0,0 +1,23 @@
+import random
+
+from evolalg.base.frams_step import FramsStep
+from evolalg.base.individual import Individual
+
+
+class FramsMutation(FramsStep):
+    def __init__(self, frams_lib, commands, mutate_prob):
+        super().__init__(frams_lib, commands)
+        self.mutate_prob = mutate_prob
+
+    def call(self, population):
+        idx = []
+        for i in range(len(population)):
+            if random.random() < self.mutate_prob:
+                idx.append(i)
+        mutated = [population[_].genotype for _ in idx]
+        mutated = self.frams.mutate([_ for _ in mutated])
+
+        for i, m in zip(idx, mutated):
+            population[i] = Individual(m)
+
+        return population
Index: /framspy/evolalg/population/frams_population.py
===================================================================
--- /framspy/evolalg/population/frams_population.py	(revision 1113)
+++ /framspy/evolalg/population/frams_population.py	(revision 1113)
@@ -0,0 +1,16 @@
+from evolalg.base.frams_step import FramsStep
+from evolalg.base.individual import Individual
+
+
+class FramsPopulation(FramsStep):
+    def __init__(self, frams_lib, genetic_format, pop_size, commands=None):
+        if commands is None:
+            commands = []
+        super().__init__(frams_lib, commands)
+
+        self.pop_size = pop_size
+        self.genetic_format = genetic_format
+
+    def call(self, *args, **kwargs):
+        return [Individual(self.frams.getSimplest(self.genetic_format)) for _ in range(self.pop_size)]
+
Index: /framspy/evolalg/repair/__init__.py
===================================================================
--- /framspy/evolalg/repair/__init__.py	(revision 1113)
+++ /framspy/evolalg/repair/__init__.py	(revision 1113)
@@ -0,0 +1,1 @@
+
Index: /framspy/evolalg/repair/const.py
===================================================================
--- /framspy/evolalg/repair/const.py	(revision 1113)
+++ /framspy/evolalg/repair/const.py	(revision 1113)
@@ -0,0 +1,12 @@
+import copy
+
+from evolalg.repair.repair import Repair
+
+
+class ConstRepair(Repair):
+    def __init__(self, value, excepted_size):
+        super(ConstRepair, self).__init__(excepted_size)
+        self.value = value
+
+    def generate_new(self, population, missing_count):
+        return copy.deepcopy(self.value)
Index: /framspy/evolalg/repair/halloffame_repair.py
===================================================================
--- /framspy/evolalg/repair/halloffame_repair.py	(revision 1113)
+++ /framspy/evolalg/repair/halloffame_repair.py	(revision 1113)
@@ -0,0 +1,16 @@
+import copy
+import random
+
+from evolalg.repair import Repair
+
+
+class HallOfFameRepair(Repair):
+    def __init__(self, excepted_size, halloffame, top):
+        super(HallOfFameRepair, self).__init__(excepted_size)
+        self.halloffame = halloffame
+        self.top = top
+
+    def generate_new(self, population, missing_count):
+        ind = random.randint(0, min(self.top, len(self.halloffame)) - 1)
+        return copy.deepcopy(population[ind])
+
Index: /framspy/evolalg/repair/multistep.py
===================================================================
--- /framspy/evolalg/repair/multistep.py	(revision 1113)
+++ /framspy/evolalg/repair/multistep.py	(revision 1113)
@@ -0,0 +1,14 @@
+from evolalg.repair.repair import Repair
+import copy
+
+class MultistepRepair(Repair):
+    def __init__(self, selection, steps, excepted_size):
+        super(MultistepRepair, self).__init__(excepted_size)
+        self.selection = selection
+        self.steps = steps
+
+    def generate_new(self, population, missing_count):
+        selected = self.selection(population, missing_count)
+        selected = copy.deepcopy(selected)
+        selected = self.steps(selected)
+        return selected
Index: /framspy/evolalg/repair/mutate.py
===================================================================
--- /framspy/evolalg/repair/mutate.py	(revision 1113)
+++ /framspy/evolalg/repair/mutate.py	(revision 1113)
@@ -0,0 +1,19 @@
+import random
+import copy
+
+from evolalg.repair.repair import Repair
+
+
+class MutateRepair(Repair):
+    def __init__(self, mutate_step, excepted_size, iterations=1):
+        super(MutateRepair, self).__init__(excepted_size)
+        self.mutate_step = mutate_step
+        self.iterations = iterations
+
+
+    def generate_new(self, population, missing_count):
+        selected = population[random.randint(0, len(population))]
+        selected = copy.deepcopy(selected)
+        for _ in range(self.iterations):
+            selected = self.mutate_step([selected])[0]
+        return selected
Index: /framspy/evolalg/repair/remove/field.py
===================================================================
--- /framspy/evolalg/repair/remove/field.py	(revision 1113)
+++ /framspy/evolalg/repair/remove/field.py	(revision 1113)
@@ -0,0 +1,12 @@
+from evolalg.repair.remove.remove import Remove
+
+
+class FieldRemove(Remove):
+    def __init__(self, field_name, field_value):
+        super(FieldRemove, self).__init__()
+        self.field_name = field_name
+        self.field_value = field_value
+
+    def remove(self, individual):
+        return not hasattr(individual, self.field_name) or getattr(individual, self.field_name) == self.field_value
+
Index: /framspy/evolalg/repair/remove/function.py
===================================================================
--- /framspy/evolalg/repair/remove/function.py	(revision 1113)
+++ /framspy/evolalg/repair/remove/function.py	(revision 1113)
@@ -0,0 +1,10 @@
+
+
+from evolalg.repair.remove.remove import Remove
+class LambdaRemove(Remove):
+    def __init__(self, func):
+        super(LambdaRemove, self).__init__()
+        self.func = func
+
+    def remove(self, individual):
+        return self.func(individual)
Index: /framspy/evolalg/repair/remove/remove.py
===================================================================
--- /framspy/evolalg/repair/remove/remove.py	(revision 1113)
+++ /framspy/evolalg/repair/remove/remove.py	(revision 1113)
@@ -0,0 +1,15 @@
+from abc import abstractmethod
+from evolalg.base.step import Step
+
+
+
+class Remove(Step):
+    def __init__(self):
+        pass
+
+    @abstractmethod
+    def remove(self, individual):
+        pass
+
+    def call(self, population):
+        return [_ for _ in population if not self.remove(_)]
Index: /framspy/evolalg/repair/repair.py
===================================================================
--- /framspy/evolalg/repair/repair.py	(revision 1113)
+++ /framspy/evolalg/repair/repair.py	(revision 1113)
@@ -0,0 +1,24 @@
+from abc import abstractmethod
+from collections import Iterable
+
+from evolalg.base.step import Step
+
+
+class Repair(Step):
+    def __init__(self, excepted_size):
+        self.excepted_size = excepted_size
+
+    @abstractmethod
+    def generate_new(self, population, missing_count):
+        pass
+
+    def call(self, population):
+        generated = []
+        while len(generated) + len(population) < self.excepted_size:
+            gen = self.generate_new(population, self.excepted_size-len(population)-len(generated))
+            if isinstance(gen, Iterable):
+                generated.extend(gen)
+            else:
+                generated.append(gen)
+        population.extend(generated)
+        return population[:self.excepted_size]
Index: /framspy/evolalg/selection/identity.py
===================================================================
--- /framspy/evolalg/selection/identity.py	(revision 1113)
+++ /framspy/evolalg/selection/identity.py	(revision 1113)
@@ -0,0 +1,18 @@
+import copy
+
+from evolalg.base.step import Step
+from evolalg.selection.selection import Selection
+
+
+class IdentitySelection(Selection):
+    def __init__(self, copy=False):
+        super(IdentitySelection, self).__init__(copy)
+
+    def call(self, population, selection_size=None):
+        res = population
+        if selection_size is not None:
+            res = population[:selection_size]
+
+        if self.copy:
+            res = copy.deepcopy(res)
+        return res
Index: /framspy/evolalg/selection/selection.py
===================================================================
--- /framspy/evolalg/selection/selection.py	(revision 1113)
+++ /framspy/evolalg/selection/selection.py	(revision 1113)
@@ -0,0 +1,25 @@
+from abc import abstractmethod
+
+from evolalg.base.step import Step
+import copy
+
+
+class Selection(Step):
+    def __init__(self, copy=False):
+        self.copy = copy
+
+    @abstractmethod
+    def select_next(self, population):
+        pass
+
+    def call(self, population, count=None):
+        res = []
+        if count is None:
+            count = len(population)
+
+        for _ in range(count):
+            sel = self.select_next(population)
+            if self.copy:
+                sel = copy.deepcopy(sel)
+            res.append(sel)
+        return res
Index: /framspy/evolalg/selection/tournament.py
===================================================================
--- /framspy/evolalg/selection/tournament.py	(revision 1113)
+++ /framspy/evolalg/selection/tournament.py	(revision 1113)
@@ -0,0 +1,17 @@
+from evolalg.base.individual import Individual
+from typing import List
+import random
+
+from evolalg.base.step import Step
+from evolalg.selection.selection import Selection
+
+
+class TournamentSelection(Selection):
+    def __init__(self, tournament_size: int, fit_attr="fitness", copy=False):
+        super(TournamentSelection, self).__init__(copy)
+        self.tournament_size = tournament_size
+        self.fit_attr = fit_attr
+
+    def select_next(self, population):
+        selected = [random.choice(population) for i in range(self.tournament_size)]
+        return max(selected, key=lambda x: getattr(x, self.fit_attr))
Index: /framspy/evolalg/statistics/halloffame_custom.py
===================================================================
--- /framspy/evolalg/statistics/halloffame_custom.py	(revision 1113)
+++ /framspy/evolalg/statistics/halloffame_custom.py	(revision 1113)
@@ -0,0 +1,64 @@
+from deap.tools import HallOfFame
+import operator
+from copy import deepcopy
+from bisect import bisect_right
+
+class HallOfFameCustom(HallOfFame):
+    def __init__(self,  maxsize, similar=operator.eq, fitness_field="fitness"):
+        super(HallOfFameCustom, self).__init__(maxsize, similar)
+        print("HOF_size =",self.maxsize)
+        self._fitness_field = fitness_field
+
+    def extract_fitness(self, ind):
+        return getattr(ind, self._fitness_field)
+
+    def update(self, population):
+        """Update the hall of fame with the *population* by replacing the
+        worst individuals in it by the best individuals present in
+        *population* (if they are better). The size of the hall of fame is
+        kept constant.
+        
+        :param population: A list of individual with a fitness attribute to
+                           update the hall of fame with.
+        """  
+        for ind in population:
+            if len(self) == 0 and self.maxsize != 0:
+                # Working on an empty hall of fame is problematic for the
+                # "for else"
+                for hofer in self:
+                    if self.similar(ind, hofer):
+                        break
+                else:
+                    self.insert(population[0])
+                
+                continue
+            if self.extract_fitness(ind) >= self.extract_fitness(self[-1]) or len(self) < self.maxsize:
+                for hofer in self:
+                    # Loop through the hall of fame to check for any
+                    # similar individual
+                    if self.similar(ind, hofer):
+                        break
+                else:
+                    # The individual is unique and strictly better than
+                    # the worst
+                    if len(self) >= self.maxsize:
+                        self.remove(-1)
+                    self.insert(ind)
+
+    def insert(self, item):
+        """Insert a new individual in the hall of fame using the
+        :func:`~bisect.bisect_right` function. The inserted individual is
+        inserted on the right side of an equal individual. Inserting a new
+        individual in the hall of fame also preserve the hall of fame's order.
+        This method **does not** check for the size of the hall of fame, in a
+        way that inserting a new individual in a full hall of fame will not
+        remove the worst individual to maintain a constant size.
+
+        :param item: The individual with a fitness attribute to insert in the
+                     hall of fame.
+        """
+        item = deepcopy(item)
+        i = bisect_right(self.keys, self.extract_fitness(item))
+        self.items.insert(len(self) - i, item)
+        self.keys.insert(i, self.extract_fitness(item))
+
Index: /framspy/evolalg/statistics/halloffame_stats.py
===================================================================
--- /framspy/evolalg/statistics/halloffame_stats.py	(revision 1113)
+++ /framspy/evolalg/statistics/halloffame_stats.py	(revision 1113)
@@ -0,0 +1,13 @@
+from evolalg.statistics.halloffame_custom import HallOfFameCustom
+from evolalg.statistics.statistics import Statistics
+
+
+class HallOfFameStatistics(Statistics):
+    def __init__(self, size, fields="fitness"):
+        self.haloffame = HallOfFameCustom(size, fitness_field=fields)
+
+    def init(self):
+        self.haloffame.clear()
+
+    def collect(self, population):
+        self.haloffame.update(population)
Index: /framspy/evolalg/statistics/statistics.py
===================================================================
--- /framspy/evolalg/statistics/statistics.py	(revision 1113)
+++ /framspy/evolalg/statistics/statistics.py	(revision 1113)
@@ -0,0 +1,14 @@
+from abc import abstractmethod
+
+from evolalg.base.step import Step
+
+
+class Statistics(Step):
+
+    @abstractmethod
+    def collect(self, population):
+        pass
+
+    def call(self, population):
+        self.collect(population)
+        return population
Index: /framspy/evolalg/statistics/statistics_deap.py
===================================================================
--- /framspy/evolalg/statistics/statistics_deap.py	(revision 1113)
+++ /framspy/evolalg/statistics/statistics_deap.py	(revision 1113)
@@ -0,0 +1,26 @@
+from deap import tools
+
+from evolalg.statistics.statistics import Statistics
+
+
+class StatisticsDeap(Statistics):
+    def __init__(self, stats, extract_fn=lambda ind: ind.fitness, verbose=True):
+        self.stats = tools.Statistics(extract_fn)
+        for name, fn in stats:
+            self.stats.register(name, fn)
+
+        self.logbook = tools.Logbook()
+        self.logbook.header = ['gen'] + (self.stats.fields if self.stats else [])
+
+        self.gen = 0
+        self.verbose = verbose
+
+    def init(self):
+        self.gen = 0
+
+    def collect(self, population):
+        record = self.stats.compile(population)
+        self.logbook.record(gen=self.gen, **record)
+        self.gen += 1
+        if self.verbose:
+            print(self.logbook.stream)
Index: /framspy/evolalg/utils/population_save.py
===================================================================
--- /framspy/evolalg/utils/population_save.py	(revision 1113)
+++ /framspy/evolalg/utils/population_save.py	(revision 1113)
@@ -0,0 +1,35 @@
+from evolalg.base.step import Step
+import os
+from framsfiles import writer as framswriter
+
+
+class PopulationSave(Step):
+    def __init__(self, path, fields, provider=None):
+        self.path = path
+        self.provider = provider
+        self.fields = fields
+
+    @staticmethod
+    def ensure_dir(path):
+        directory = os.path.dirname(path)
+        if directory == "":
+            return
+        if not os.path.exists(directory):
+            os.makedirs(directory)
+
+    def call(self, population):
+        PopulationSave.ensure_dir(self.path)
+        provider = self.provider
+        if provider is None:
+            provider = population
+
+        #TODO instead of "fitness", write all fields used as fitness source with their original names (e.g. "velocity", "vertpos" etc.). In evaluation, set all attributes we get from Framsticks so that here we get all original names and values. Or, even better, introduce a dict field in Individual and assign to it everything that we get from Framsticks on evaluation (and add a filtering ability, i.e. when None - save all, when a list of field names - save only the enumerated fields)
+        with open(self.path, "w") as outfile:
+            for ind in provider:
+                keyval = {}
+                for k in self.fields: # construct a dictionary with criteria names and their values
+                    keyval[k] = getattr(ind, self.fields[k])
+                outfile.write(framswriter.from_collection({"_classname": "org", **keyval}))
+                outfile.write("\n")
+
+        return population
Index: /framspy/evolalg/utils/stable_generation.py
===================================================================
--- /framspy/evolalg/utils/stable_generation.py	(revision 1113)
+++ /framspy/evolalg/utils/stable_generation.py	(revision 1113)
@@ -0,0 +1,37 @@
+from collections import Iterable
+
+from evolalg.base.step import Step
+import copy
+
+from evolalg.base.union_step import UnionStep
+
+
+class StableGeneration(Step):
+
+    def __init__(self, selection, steps, population_size=None):
+        self.selection = selection
+        self.steps = UnionStep(steps)
+        self.population_size = population_size
+
+    def generate_new(self, population, missing_count):
+        selected = self.selection(population, missing_count)
+        selected = copy.deepcopy(selected)
+        selected = self.steps(selected)
+        return selected
+
+    def init(self):
+        self.selection.init()
+        self.steps.init()
+
+    def call(self, population):
+        population_size = self.population_size
+        if population_size is None:
+            population_size = len(population)
+        generated = []
+        while len(generated) < population_size:
+            gen = self.generate_new(population, population_size - len(generated))
+            if isinstance(gen, Iterable):
+                generated.extend(gen)
+            else:
+                generated.append(gen)
+        return generated[:len(population)]
Index: /framspy/run-evolalg-examples.cmd
===================================================================
--- /framspy/run-evolalg-examples.cmd	(revision 1113)
+++ /framspy/run-evolalg-examples.cmd	(revision 1113)
@@ -0,0 +1,10 @@
+rem To learn about all available options of each .py algorithm, add "-h" to its parameters.
+rem Use the source code of the examples as a starting point for your customizations.
+rem Example usage:
+
+set PATH_TO_DIR_WITH_FRAMS_LIBRARY=............
+
+
+python -m evolalg.examples.standard          -path %PATH_TO_DIR_WITH_FRAMS_LIBRARY%   -opt numneurons
+
+python -m evolalg.examples.niching_novelty   -path %PATH_TO_DIR_WITH_FRAMS_LIBRARY%   -opt velocity
