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

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

Introduced the number of generations as a command-line parameter; refactored; fixed a typo

File size: 9.8 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. Single or multiple criteria.')
59    parser.add_argument('-lib', required=False, help="Filename of .so or .dll with 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 .sim file")
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="Size of population, default 50.")
68    parser.add_argument('-generations', type=int, default=5, help="Number of generations, default 5.")
69    parser.add_argument('-num_parts', type=int, default=None, help="Maximum number of parts. Default None")
70    parser.add_argument('-checkpoint_path', required=False, default=None, help="Path to the checkpoint file")
71    parser.add_argument('-checkpoint_interval', required=False, type=int, default=100, help="Checkpoint interval")
72    return parser.parse_args()
73
74
75def extract_fitness(ind):
76    return ind.fitness_raw
77
78
79def print_population_count(pop):
80    print("Current:", len(pop))
81    return pop  # Each step must return a population
82
83
84class NumPartsGreater(Remove):
85    def __init__(self, numparts):
86        super(NumPartsGreater, self).__init__()
87        self.numparts = numparts
88
89    def remove(self, individual):
90        return individual.numparts > self.numparts
91
92
93def func_niching(ind): setattr(ind, "fitness", ind.fitness_raw * (1 + ind.dissim))
94
95
96def func_raw(ind): setattr(ind, "fitness", ind.fitness_raw)
97
98
99def func_novelty(ind): setattr(ind, "fitness", ind.dissim)
100
101
102def load_experiment(path):
103    with open(path, "rb") as file:
104        experiment = pickle.load(file)
105    print("Loaded experiment. Generation:", experiment.generation)
106    return experiment
107
108
109def create_experiment():
110    parsed_args = parseArguments()
111    frams = FramsticksLib(parsed_args.path, parsed_args.lib,
112                          parsed_args.sim)
113    # Steps for generating first population
114    init_stages = [
115        FramsPopulation(frams, parsed_args.genformat, parsed_args.popsize)
116    ]
117
118    # Selection procedure
119    selection = TournamentSelection(5,
120                                    copy=True)  # 'fitness' by default, the targeted attribute can be changed, e.g. fit_attr="fitness_raw"
121
122    # Procedure for generating new population. This steps will be run as long there is less than
123    # popsize individuals in the new population
124    new_generation_stages = [FramsCrossAndMutate(frams, cross_prob=0.2, mutate_prob=0.9)]
125
126    # Steps after new population is created. Executed exacly once per generation.
127    generation_modifications = []
128
129    # -------------------------------------------------
130    # Fitness
131
132    fitness_raw = FitnessStep(frams, fields={parsed_args.opt: "fitness_raw", "numparts": "numparts"},
133                              fields_defaults={parsed_args.opt: None, "numparts": float("inf")},
134                              evaluation_count=1)
135
136    fitness_end = FitnessStep(frams, fields={parsed_args.opt: "fitness_raw"},
137                              fields_defaults={parsed_args.opt: None},
138                              evaluation_count=100)  # evaluate the contents of the last population 100 times (TODO replace this approach and evaluate HOF instead of the last population)
139    # Remove
140    remove = []
141    remove.append(FieldRemove("fitness_raw", None))  # Remove individuals if they have default value for fitness
142    if parsed_args.num_parts is not None:
143        # This could be also implemented by "LambdaRemove(lambda x: x.numparts > parsed_args.num_parts)
144        # But this will not serialize in checkpoint.
145        remove.append(NumPartsGreater(parsed_args.num_parts))
146    remove_step = UnionStep(remove)
147
148    fitness_remove = UnionStep([fitness_raw, remove_step])
149
150    init_stages.append(fitness_remove)
151    new_generation_stages.append(fitness_remove)
152
153    # -------------------------------------------------
154    # Novelty or niching
155    dissim = None
156    if parsed_args.dissim == Dissim.levenshtein:
157        dissim = LevenshteinDissimilarity(reduction="mean", output_field="dissim")
158    elif parsed_args.dissim == Dissim.frams:
159        dissim = FramsDissimilarity(frams, reduction="mean", output_field="dissim")
160
161    if parsed_args.fit == Fitness.raw:
162        # Fitness is equal to finess raw
163        raw = LambdaStep(func_raw)
164        init_stages.append(raw)
165        new_generation_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        new_generation_stages.append(niching)
175        generation_modifications.append(niching)
176
177    if parsed_args.fit == Fitness.novelty:
178        novelty = UnionStep([
179            dissim,
180            LambdaStep(func_novelty)
181        ])
182        init_stages.append(novelty)
183        new_generation_stages.append(novelty)
184        generation_modifications.append(novelty)
185
186    # -------------------------------------------------
187    # Statistics
188    hall_of_fame = HallOfFameStatistics(100, "fitness_raw")  # Wrapper for halloffamae
189    statistics_deap = StatisticsDeap([
190        ("avg", np.mean),
191        ("stddev", np.std),
192        ("min", np.min),
193        ("max", np.max)
194    ], extract_fitness)  # Wrapper for deap statistics
195
196    statistics_union = UnionStep([
197        hall_of_fame,
198        statistics_deap
199    ])  # Union of two statistics steps.
200
201    init_stages.append(statistics_union)
202    generation_modifications.append(statistics_union)
203
204    # -------------------------------------------------
205    # End stages: this will execute exacly once after all generations.
206    end_stages = [
207        fitness_end,
208        PopulationSave("halloffame.gen", provider=hall_of_fame.halloffame, fields={"genotype": "genotype",
209                                                                                  "fitness": "fitness_raw"})]
210    # ...but custom fields can be added, e.g. "custom": "recording"
211
212    # -------------------------------------------------
213    # Experiment creation
214
215    experiment = Experiment(init_population=init_stages,
216                            selection=selection,
217                            new_generation_steps=new_generation_stages,
218                            generation_modification=generation_modifications,
219                            end_steps=end_stages,
220                            population_size=parsed_args.popsize,
221                            checkpoint_path=parsed_args.checkpoint_path,
222                            checkpoint_interval=parsed_args.checkpoint_interval
223                            )
224    return experiment
225
226
227def main():
228    print("Running experiment with", sys.argv)
229    parsed_args = parseArguments()
230
231    if os.path.exists(parsed_args.checkpoint_path):
232        experiment = load_experiment(parsed_args.checkpoint_path)
233        FramsticksLib(parsed_args.path, parsed_args.lib,
234                      parsed_args.sim)
235    else:
236        experiment = create_experiment()
237        experiment.init()  # init is mandatory
238
239
240    experiment.run(parsed_args.generations)
241
242    # Next call for experiment.run(10) will do nothing. Parameter 10 specifies how many generations should be
243    # in one experiment. Previous call generated 10 generations.
244    # Example 1:
245    # experiment.init()
246    # experiment.run(10)
247    # experiment.run(12)
248    # #This will run for total of 12 generations
249    #
250    # Example 2
251    # experiment.init()
252    # experiment.run(10)
253    # experiment.init()
254    # experiment.run(10)
255    # # All work produced by first run will be "destroyed" by second init().
256
257
258
259if __name__ == '__main__':
260    main()
Note: See TracBrowser for help on using the repository browser.