source: framspy/FramsticksLibCompetition.py

Last change on this file was 1207, checked in by Maciej Komosinski, 14 months ago

Examples made more friendly to shells that treat semicolon "as;a;command;separator" (like bash or powershell)

File size: 5.2 KB
Line 
1import sys
2from time import perf_counter, strftime
3from typing import List  # to be able to specify a type hint of list(something)
4import numpy as np
5from FramsticksLib import FramsticksLib
6from base64 import urlsafe_b64encode
7
8
9class FramsticksLibCompetition(FramsticksLib):
10        """A proxy to FramsticksLib.py with the same interface, but recording the highest achieved fitness and limiting the number of evaluation calls.
11        Use it in the same way as FramsticksLib.py.
12        For the use in competition, remember to call end() when your algorithm completes.
13        To run a working example, follow these four steps:
14        - set STORE_ALL_PART_COORDS = 0 in recording-body-coords.sim,
15        - set SIMPLE_FITNESS_FORMAT = False below,
16        - edit FramsticksEvolution.py in a few places so that it only uses the FramsticksLibCompetition class instead of FramsticksLib,
17        - run: python FramsticksEvolution.py -path %DIR_WITH_FRAMS_LIBRARY%  -sim "eval-allcriteria.sim;deterministic.sim;recording-body-coords.sim"  -opt COGpath  -generations 20
18        See also: https://www.framsticks.com/gecco-competition
19        """
20
21        COMPETITOR_ID = 'AliceTeam'
22        SIMPLE_FITNESS_FORMAT = True  # set to False only if you want compatibility with existing sources of optimization algorithms such as FramsticksEvolution.py. Otherwise (for True), you will just get a simple number as fitness.
23        FITNESS_DICT_KEY = 'COGpath'  # only used for SIMPLE_FITNESS_FORMAT = False
24
25        MAX_EVALUATIONS = 100_000  # 100k
26        MAX_TIME = 60 * 60 * 1  # 1h (excluding evaluation time)
27
28        TEST_FUNCTION = 3
29
30        _best_fitness = None
31        _best_solution = None
32        _evaluation_count = 0
33        _evaluation_time = 0  # used to exclude solution evaluation time from the total running time
34        _time0 = perf_counter()
35
36
37        def _evaluate_path(self, path):
38                path = np.array(path)
39                if self.TEST_FUNCTION == 3:
40                        return np.linalg.norm(path[0] - path[-1])  # simple example: returns distance between COG locations of birth and death.
41                elif self.TEST_FUNCTION == 4:
42                        return np.linalg.norm(path[0] - path[-1]) * np.mean(np.maximum(0, path[:, 2]))  # simple example: run far and have COG high above ground!
43                elif self.TEST_FUNCTION == 5:
44                        return 1000 - np.linalg.norm(np.linspace(0, 10, len(path), endpoint=True) - path[:, 2]) / np.sqrt(len(path))  # simple example: z coordinate of the COG should grow linearly from 0 to 1 during lifespan. Returns RMSE as a deviation measure (negative because we are maximizing, and offset to ensure positive outcomes so there is no clash with other optimization code that may assume that negative fitness indicates an invalid genotype).
45                raise RuntimeError('TEST_FUNCTION==%s not implemented!' % self.TEST_FUNCTION)
46
47
48        def _evaluate_single_genotype(self, genotype):
49                self._evaluation_count += 1
50                if self._evaluation_count > self.MAX_EVALUATIONS or perf_counter() - self._time0 - self._evaluation_time > self.MAX_TIME:
51                        print('The allowed time or the number of evaluations exceeded')
52                        self.end()  # exits the program
53                result = super().evaluate([genotype])  # sample result for invalid genotype: [{'num': 172, 'name': 'Agoha Syhy', 'evaluations': None}]
54                valid_result = result[0]['evaluations'] is not None
55                fitness = self._evaluate_path(result[0]['evaluations']['']['data->bodyrecording']) if valid_result else None
56                if fitness is not None and (self._best_fitness is None or self._best_fitness < fitness):
57                        self._best_fitness = fitness
58                        self._best_solution = genotype
59                if self.SIMPLE_FITNESS_FORMAT:
60                        return fitness
61                else:  # update existing structure (vector of dict of dict...)
62                        if valid_result:
63                                result[0]['evaluations'][''][self.FITNESS_DICT_KEY] = fitness
64                        else:
65                                # result[0]['evaluations'] = {'': {self.FITNESS_KEY: fitness}}  # [{'num': 260, 'name': 'Imepak Syhy', 'evaluations': {'': {'path': None}}}]
66                                pass  # leave 'result' as it is, the caller expects such an incomplete structure (with 'evaluations': None) on evaluation failure
67                        return result[0]
68
69
70        def evaluate(self, genotype_list: List[str]):
71                """
72                :return: a list of fitness values (see also SIMPLE_FITNESS_FORMAT), with None for genotypes that are not valid.
73                """
74                if len(genotype_list) > self.MAX_EVALUATIONS:
75                        raise RuntimeError('Too many genotypes to evaluate in one batch: %d' % len(genotype_list))
76                eval_time0 = perf_counter()
77                fitnesses = [self._evaluate_single_genotype(genotype) for genotype in genotype_list]
78                self._evaluation_time += perf_counter() - eval_time0
79                return fitnesses
80
81
82        def end(self):
83                print('Finishing... best solution =', self._best_fitness)
84
85                filename = urlsafe_b64encode(self.COMPETITOR_ID.encode()).decode() + ".results"
86                competitor = "".join(x for x in self.COMPETITOR_ID if x.isalnum())
87
88                s = strftime("%Y-%m-%d %H:%M")
89                s += "\t%s\t%d\t%d" % (competitor, self.TEST_FUNCTION, self._evaluation_count)
90
91                total_running_time = perf_counter() - self._time0
92                s += "\t%g\t%g" % (total_running_time, total_running_time - self._evaluation_time)
93
94                s += "\t" + str(self._best_fitness)
95                s += "\t" + str(self._best_solution)
96                print(s)
97                with open(filename, "a") as outfile:  # append
98                        outfile.write(s)
99                        outfile.write("\n")
100                print("Saved '%s' (%s)" % (filename, competitor))
101                sys.exit()  # only call end() once
Note: See TracBrowser for help on using the repository browser.