source: framspy/evolalg/examples/niching_novelty.py @ 1137

Last change on this file since 1137 was 1137, checked in by Maciej Komosinski, 3 years ago

Removed unnecessary (duplicated) stages

File size: 9.9 KB
Line 
1import argparse
2import os
3import pickle
4import sys
5from enum import Enum
6
7import numpy as np
8
9from FramsticksLib import FramsticksLib
10from evolalg.base.lambda_step import LambdaStep
11from evolalg.base.step import Step
12from evolalg.dissimilarity.frams_dissimilarity import FramsDissimilarity
13from evolalg.dissimilarity.levenshtein import LevenshteinDissimilarity
14from evolalg.experiment import Experiment
15from evolalg.fitness.fitness_step import FitnessStep
16from evolalg.mutation_cross.frams_cross_and_mutate import FramsCrossAndMutate
17from evolalg.population.frams_population import FramsPopulation
18from evolalg.repair.remove.field import FieldRemove
19from evolalg.repair.remove.remove import Remove
20from evolalg.selection.tournament import TournamentSelection
21from evolalg.statistics.halloffame_stats import HallOfFameStatistics
22from evolalg.statistics.statistics_deap import StatisticsDeap
23from evolalg.base.union_step import UnionStep
24from evolalg.utils.population_save import PopulationSave
25import time
26
27
28def ensureDir(string):
29    if os.path.isdir(string):
30        return string
31    else:
32        raise NotADirectoryError(string)
33
34
35class Dissim(Enum):
36    levenshtein = "levenshtein"
37    frams = "frams"
38
39    def __str__(self):
40        return self.name
41
42
43class Fitness(Enum):
44    raw = "raw"
45    niching = "niching"
46    novelty = "novelty"
47
48    def __str__(self):
49        return self.name
50
51
52def parseArguments():
53    parser = argparse.ArgumentParser(
54        description='Run this program with "python -u %s" if you want to disable buffering of its output.' % sys.argv[
55            0])
56    parser.add_argument('-path', type=ensureDir, required=True, help='Path to the Framsticks library without trailing slash.')
57    parser.add_argument('-opt', required=True,
58                        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.')
59    parser.add_argument('-lib', required=False, help="Filename of .so or .dll with the Framsticks library")
60    parser.add_argument('-genformat', required=False, default="1",
61                        help='Genetic format for the demo run, for example 4, 9, or B. If not given, f1 is assumed.')
62    parser.add_argument('-sim', required=False, default="eval-allcriteria.sim", help="Name of the .sim file with all parameter values")
63    parser.add_argument('-dissim', required=False, type=Dissim, default=Dissim.frams,
64                        help='Dissimilarity measure, default = frams', choices=list(Dissim))
65    parser.add_argument('-fit', required=False, default=Fitness.raw, type=Fitness,
66                        help=' Fitness criteria DEFAULT = raw', choices=list(Fitness))
67    parser.add_argument('-popsize', type=int, default=50, help="Population size, default 50.")
68    parser.add_argument('-generations', type=int, default=5, help="Number of generations, default 5.")
69    parser.add_argument('-tournament', type=int, default=5, help="Tournament size, default 5.")
70    parser.add_argument('-num_parts', type=int, default=None, help="Maximum number of Parts. Default: no limit")
71    parser.add_argument('-checkpoint_path', required=False, default=None, help="Path to the checkpoint file")
72    parser.add_argument('-checkpoint_interval', required=False, type=int, default=100, help="Checkpoint interval")
73    return parser.parse_args()
74
75
76def extract_fitness(ind):
77    return ind.fitness_raw
78
79
80def print_population_count(pop):
81    print("Current popsize:", len(pop))
82    return pop  # Each step must return a population
83
84
85class NumPartsGreater(Remove):
86    def __init__(self, numparts):
87        super(NumPartsGreater, self).__init__()
88        self.numparts = numparts
89
90    def remove(self, individual):
91        return individual.numparts > self.numparts
92
93
94def func_niching(ind): setattr(ind, "fitness", ind.fitness_raw * (1 + ind.dissim))
95
96
97def func_raw(ind): setattr(ind, "fitness", ind.fitness_raw)
98
99
100def func_novelty(ind): setattr(ind, "fitness", ind.dissim)
101
102
103def load_experiment(path):
104    with open(path, "rb") as file:
105        experiment = pickle.load(file)
106    print("Loaded experiment. Generation:", experiment.generation)
107    return experiment
108
109
110def create_experiment():
111    parsed_args = parseArguments()
112    frams = FramsticksLib(parsed_args.path, parsed_args.lib,
113                          parsed_args.sim)
114    # Steps for generating first population
115    init_stages = [
116        FramsPopulation(frams, parsed_args.genformat, parsed_args.popsize)
117    ]
118
119    # Selection procedure
120    selection = TournamentSelection(parsed_args.tournament,
121                                    copy=True)  # 'fitness' by default, the targeted attribute can be changed, e.g. fit_attr="fitness_raw"
122
123    # Procedure for generating new population. This steps will be run as long there is less than
124    # popsize individuals in the new population
125    new_generation_stages = [FramsCrossAndMutate(frams, cross_prob=0.2, mutate_prob=0.9)]
126
127    # Steps after new population is created. Executed exacly once per generation.
128    generation_modifications = []
129
130    # -------------------------------------------------
131    # Fitness
132
133    fitness_raw = FitnessStep(frams, fields={parsed_args.opt: "fitness_raw", "numparts": "numparts"},
134                              fields_defaults={parsed_args.opt: None, "numparts": float("inf")},
135                              evaluation_count=1)
136
137    fitness_end = FitnessStep(frams, fields={parsed_args.opt: "fitness_raw"},
138                              fields_defaults={parsed_args.opt: None},
139                              evaluation_count=100)  # evaluate the contents of the last population 100 times (TODO replace this approach and evaluate HOF instead of the last population)
140    # Remove
141    remove = []
142    remove.append(FieldRemove("fitness_raw", None))  # Remove individuals if they have default value for fitness
143    if parsed_args.num_parts is not None:
144        # This could be also implemented by "LambdaRemove(lambda x: x.numparts > parsed_args.num_parts)
145        # But this will not serialize in checkpoint.
146        remove.append(NumPartsGreater(parsed_args.num_parts))
147    remove_step = UnionStep(remove)
148
149    fitness_remove = UnionStep([fitness_raw, remove_step])
150
151    init_stages.append(fitness_remove)
152    new_generation_stages.append(fitness_remove)
153
154    # -------------------------------------------------
155    # Novelty or niching
156    dissim = None
157    if parsed_args.dissim == Dissim.levenshtein:
158        dissim = LevenshteinDissimilarity(reduction="mean", output_field="dissim")
159    elif parsed_args.dissim == Dissim.frams:
160        dissim = FramsDissimilarity(frams, reduction="mean", output_field="dissim")
161
162    if parsed_args.fit == Fitness.raw:
163        # Fitness is equal to finess raw
164        raw = LambdaStep(func_raw)
165        init_stages.append(raw)
166        generation_modifications.append(raw)
167
168    if parsed_args.fit == Fitness.niching:
169        niching = UnionStep([
170            dissim,
171            LambdaStep(func_niching)
172        ])
173        init_stages.append(niching)
174        generation_modifications.append(niching)
175
176    if parsed_args.fit == Fitness.novelty:
177        novelty = UnionStep([
178            dissim,
179            LambdaStep(func_novelty)
180        ])
181        init_stages.append(novelty)
182        generation_modifications.append(novelty)
183
184    # -------------------------------------------------
185    # Statistics
186    hall_of_fame = HallOfFameStatistics(100, "fitness_raw")  # Wrapper for halloffamae
187    statistics_deap = StatisticsDeap([
188        ("avg", np.mean),
189        ("stddev", np.std),
190        ("min", np.min),
191        ("max", np.max)
192    ], extract_fitness)  # Wrapper for deap statistics
193
194    statistics_union = UnionStep([
195        hall_of_fame,
196        statistics_deap
197    ])  # Union of two statistics steps.
198
199    init_stages.append(statistics_union)
200    generation_modifications.append(statistics_union)
201
202    # -------------------------------------------------
203    # End stages: this will execute exacly once after all generations.
204    end_stages = [
205        fitness_end,
206        PopulationSave("halloffame.gen", provider=hall_of_fame.halloffame, fields={"genotype": "genotype",
207                                                                                  "fitness": "fitness_raw"})]
208    # ...but custom fields can be added, e.g. "custom": "recording"
209
210    # -------------------------------------------------
211    # Experiment creation
212
213    experiment = Experiment(init_population=init_stages,
214                            selection=selection,
215                            new_generation_steps=new_generation_stages,
216                            generation_modification=generation_modifications,
217                            end_steps=end_stages,
218                            population_size=parsed_args.popsize,
219                            checkpoint_path=parsed_args.checkpoint_path,
220                            checkpoint_interval=parsed_args.checkpoint_interval
221                            )
222    return experiment
223
224
225def main():
226    print("Running experiment with", sys.argv)
227    parsed_args = parseArguments()
228
229    if parsed_args.checkpoint_path is not None and os.path.exists(parsed_args.checkpoint_path):
230        experiment = load_experiment(parsed_args.checkpoint_path)
231        FramsticksLib(parsed_args.path, parsed_args.lib,
232                      parsed_args.sim)
233    else:
234        experiment = create_experiment()
235        experiment.init()  # init is mandatory
236
237
238    experiment.run(parsed_args.generations)
239
240    # Next call for experiment.run(10) will do nothing. Parameter 10 specifies how many generations should be
241    # in one experiment. Previous call generated 10 generations.
242    # Example 1:
243    # experiment.init()
244    # experiment.run(10)
245    # experiment.run(12)
246    # #This will run for total of 12 generations
247    #
248    # Example 2
249    # experiment.init()
250    # experiment.run(10)
251    # experiment.init()
252    # experiment.run(10)
253    # # All work produced by first run will be "destroyed" by second init().
254
255
256
257if __name__ == '__main__':
258    main()
Note: See TracBrowser for help on using the repository browser.