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

Last change on this file since 1138 was 1138, checked in by Maciej Komosinski, 21 months ago

Added limits for the number of Parts, Joints, Neurons and Neural connections

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