Browse Source

Enhanced __main__ and add mutator

Yann Weber 6 years ago
parent
commit
8b6aa86c33
4 changed files with 186 additions and 19 deletions
  1. 78
    15
      gte/__main__.py
  2. 70
    0
      gte/mutator.py
  3. 3
    1
      gte/rpnlib.py
  4. 35
    3
      gte/world.py

+ 78
- 15
gte/__main__.py View File

@@ -1,15 +1,19 @@
1 1
 import argparse
2 2
 import logging
3
+import time
3 4
 import numpy as np
4 5
 from random import randint
6
+import os.path
5 7
 
6
-logging.basicConfig(level=logging.DEBUG,
7
-                    format="%(created)d %(asctime)s:%(module)s(%(lineno)d):%(levelname)s:%(message)s %(relativeCreated)dms")
8
+logging.basicConfig(level=logging.INFO,
9
+                    format="%(created)d %(asctime)s:%(module)s(%(lineno)d):%(levelname)s:%(message)s")
8 10
 logger = logging.getLogger(__name__)
9 11
 logger.debug('Logger started')
10 12
 
11 13
 from . import turmit, rpnlib
12 14
 from .turmit import Turmit
15
+from .world import World, LivingTurmit
16
+from .mutator import mutate
13 17
 
14 18
 parser = argparse.ArgumentParser(description='Genetic Turmit Evolver')
15 19
 
@@ -23,10 +27,30 @@ parser.add_argument('--rand-sym', '-r', action='store_const', default=False,
23 27
 parser.add_argument('--steps', '-s', type=int, metavar='N', default=1024,
24 28
                     help="Number of steps")
25 29
 
30
+parser.add_argument('--try', type=int, metavar='N', default=5)
31
+
26 32
 parser.add_argument('--world-height', '-y', type=int, metavar='HEIGHT',
27
-                    default=512)
33
+                    default=256)
28 34
 parser.add_argument('--world-width', '-x', type=int, metavar='WIDTH',
29
-                    default=512)
35
+                    default=256)
36
+
37
+parser.add_argument('--turmit-count', '-T', type=int, metavar='N',
38
+                    default=10)
39
+
40
+parser.add_argument('--pool-size', '-p', type=int, metavar='N',
41
+                    default=20)
42
+
43
+parser.add_argument('--prog-size', type=int, metavar='SIZE',
44
+                    default=10)
45
+
46
+parser.add_argument('--output-dir', '-O', type=str, metavar='PATH',
47
+                    default=None)
48
+
49
+parser.add_argument('--prog', '-P', type=str, metavar='EXPR',
50
+                    default=None)
51
+
52
+parser.add_argument('--threads', type=int, metavar='N',
53
+                    default=4)
30 54
 
31 55
 args = parser.parse_args()
32 56
 
@@ -42,20 +66,59 @@ elif args.rand_sym:
42 66
         print(rpnlib.RpnSymbol.random())
43 67
     exit(0)
44 68
 
45
-from .world import World, LivingTurmit
46 69
 w = World(args.world_height, args.world_width)
47 70
 
48
-prog = rpnlib.RpnExpr.random(10)
49
-prog.append(rpnlib.RpnSymbol.rand_op())
50
-print(str(prog))
71
+if args.prog is None:
72
+    progs = [rpnlib.RpnExpr.random(args.prog_size)
73
+             for _ in range(args.pool_size)]
74
+else:
75
+    progs = [rpnlib.RpnExpr.from_string(args.prog)
76
+             for _ in range(args.pool_size)]
77
+
78
+generation = 0
79
+
80
+while True:
81
+    scores = []
82
+    logger.info('Generation %d' % generation)
83
+    genstart = time.time()
84
+    for i, prog in enumerate(progs):
85
+        
86
+        logger.debug('Running %s' % prog)
87
+        w.raz()
88
+        turmits = [LivingTurmit(world=w, prog=prog)
89
+                   for _ in range(args.turmit_count)]
90
+
91
+        start = time.time()
92
+        for step in range(args.steps):
93
+            for turmit in turmits:
94
+                turmit()
95
+        stop = time.time()
96
+
97
+        score = w.fractdim()
98
+        scores.append((score, prog))
99
+        msg = 'Prog %d/%d scores %.3f in %.2fs (%dus per step)'
100
+        msg %= (i, len(progs), score,
101
+                stop - start,
102
+                ((stop - start)*1000000)//args.steps)
103
+        logger.info(msg)
104
+        
105
+        if args.output_dir is not None:
106
+            filename = 'gte_G%04X_%02d.png' % (generation, i)
107
+            w.save(os.path.join(args.output_dir,filename))
108
+            logger.debug('Saved in %s' % filename)
109
+
110
+    genstop = time.time()
111
+    logger.info('Generation evaluating ended in %.2fs' % (genstop - genstart))
51 112
 
52
-turmits = [LivingTurmit(world=w, prog=prog) for _ in range(10)]
53
-for step in range(args.steps):
54
-    for turmit in turmits:
55
-        turmit()
113
+    scores = sorted(scores, key=lambda x: x[0], reverse=True)
114
+    for score, prog in scores[:len(scores)//2]:
115
+        logger.info('Keeping %.3f : %s' % (score, str(prog)))
56 116
 
57
-print(w.fractdim())
58
-w.save('/tmp/gte.png')
59
-print('Saved in /tmp/gte.png')
117
+    progs = [ prog for _, prog in scores[:len(scores)//2]]
118
+    for prog in list(progs):
119
+        progs.append(mutate(prog, force=True))
120
+        if len(progs) >= args.pool_size:
121
+            break
122
+    generation += 1
60 123
 
61 124
 exit(0)

+ 70
- 0
gte/mutator.py View File

@@ -0,0 +1,70 @@
1
+import copy
2
+from random import randint
3
+
4
+from .rpnlib import RpnExpr, RpnSymbol
5
+
6
+ADD = (10, 100)
7
+DEL = (5,100)
8
+UPDVAL = (25, 100)
9
+UPDTYPE = (25,100)
10
+
11
+def mutate(expr, force = True, mutcount = 1):
12
+    ''' @brief Introduce a mutation in expr
13
+        @param expr RpnExpr : an RPN expression
14
+        @param force bool : if force a mutation occur each time
15
+        @param mutcount int : count of time
16
+        @return a muatated copy of expr
17
+    '''
18
+    if force:
19
+        return mutatef(expr, mutcount)
20
+
21
+    res = copy.copy(expr)
22
+    for _ in range(mutcount):
23
+        rep = randint(*ADD)
24
+        if rep <= ADD[0]:
25
+            mutadd(res)
26
+
27
+        rep = randint(*DEL)
28
+        if rep <= DEL[0]:
29
+            mutdel(res)
30
+
31
+        rep = randint(*UPDTYPE)
32
+        if rep <= UPDTYPE[0]:
33
+            mutupdtype(res)
34
+        
35
+        rep = randint(*UPDVAL)
36
+        if rep <= UPDVAL[0]:
37
+            mutupdval(res)
38
+
39
+    return res
40
+
41
+def mutatef(expr, mutcount = 1):
42
+    mutations = [mutadd, mutdel]
43
+    mutations += [mutupdtype for _ in range(3)]
44
+    mutations += [mutupdval for _ in range(6)]
45
+    res = copy.copy(expr)
46
+    for _ in range(mutcount):
47
+        mutations[randint(0, len(mutations)-1)](res)
48
+    return res
49
+
50
+def mutadd(expr):
51
+    pos = randint(0, len(expr))
52
+    expr.insert(pos, RpnSymbol.random())
53
+    return expr
54
+
55
+def mutdel(expr):
56
+    pos = randint(0, len(expr)-1)
57
+    del(expr[pos])
58
+    return expr
59
+
60
+def mutupdtype(expr):
61
+    pos = randint(0, len(expr) - 1)
62
+    optype = [RpnSymbol.OPERATION, RpnSymbol.VALUE, RpnSymbol.VARIABLE]
63
+    del(optype[optype.index(expr[pos].optype)])
64
+    expr[pos] = RpnSymbol.random(optype[randint(0,1)])
65
+    return expr
66
+
67
+def mutupdval(expr):
68
+    pos = randint(0, len(expr) - 1)
69
+    expr[pos] = RpnSymbol(randint(0, 0xFFFF), expr[pos].optype)
70
+    return expr

+ 3
- 1
gte/rpnlib.py View File

@@ -58,7 +58,7 @@ class RpnExpr(list):
58 58
     def __setitem__(self, idx, val):
59 59
         if not isinstance(val, RpnSymbol):
60 60
             raise TypeError('RpnSymbol expected but got %s' % type(val))
61
-        super().__setitem(idx, val)
61
+        super().__setitem__(idx, val)
62 62
 
63 63
     def __str__(self):
64 64
         return ' '.join([str(sym) for sym in self])
@@ -104,6 +104,7 @@ class RpnSymbol(object):
104 104
                     self.value = value
105 105
             else:
106 106
                 self.value = list(_op_list.keys())[value % len(_op_list)]
107
+            self.value = self.value.lower()
107 108
         elif optype == self.VARIABLE:
108 109
             if isinstance(value, str):
109 110
                 if not value in _var_list:
@@ -113,6 +114,7 @@ class RpnSymbol(object):
113 114
                     self.value = value
114 115
             else:
115 116
                 self.value = list(_var_list.keys())[value % len(_var_list)]
117
+            self.value = self.value.lower()
116 118
         if err:
117 119
             msg = 'Invalid %s : "%s"' % (err_type, value.upper())
118 120
 

+ 35
- 3
gte/world.py View File

@@ -1,11 +1,40 @@
1 1
 """ @brief This module contains stuff that represent Turmit's world """
2 2
 
3
+import colorsys
3 4
 import scipy.misc
5
+import logging
4 6
 import numpy as np
5 7
 from random import randint
6 8
 
7 9
 from .turmit import Turmit
8 10
 
11
+logger = logging.getLogger(__name__)
12
+
13
+def eval_prog(prog, args):
14
+    ''' @brief Return an array of fractdim
15
+        @param args : dirty argparser returned arguments
16
+    '''
17
+    w = World(args.world_height, args.world_width)
18
+    logger.debug('Running %s' % prog)
19
+    w.raz()
20
+    turmits = [LivingTurmit(world=w, prog=prog)
21
+               for _ in range(args.turmit_count)]
22
+
23
+    start = time.time()
24
+    for step in range(args.steps):
25
+        for turmit in turmits:
26
+            turmit()
27
+    stop = time.time()
28
+
29
+    score = w.fractdim()
30
+    scores.append(score)
31
+    msg = 'Prog %d/%d scores %.3f in %.2fs (%dus per step)'
32
+    msg %= (i, len(progs), score,
33
+            stop - start,
34
+            ((stop - start)*1000000)//args.steps)
35
+    logger.info(msg)
36
+    return scores
37
+
9 38
 class World(object):
10 39
     ''' @brief A Turmit world. An array of dim == 2 with tuple(R,G,B) '''
11 40
     
@@ -109,19 +138,22 @@ class LivingTurmit(Turmit):
109 138
             msg = 'Color expected to be a tuple(R,G,B) but %s found'
110 139
             raise TypeError(msg % color)
111 140
         ## @brief Stores Turmit color
112
-        self._col = (randint(0,255), randint(0, 255), randint(0,255))
141
+        rand_hue = randint(0,255) / 255
142
+        self._col = tuple(round(i * 255)
143
+                          for i in colorsys.hsv_to_rgb(rand_hue,1,1))
144
+        #self._col = (randint(0,255), randint(0, 255), randint(0,255))
113 145
         if color is not None:
114 146
             self._col = color
115 147
         ## @brief @ref World instance
116 148
         self._world = world
117 149
 
118
-        self._x = randint(0, world.width)
150
+        self._x = randint(0, world.width - 1)
119 151
         if x is not None:
120 152
             if x < 0 or x >= world.width:
121 153
                 raise ValueError('Invalid X position %d' % x)
122 154
             self._x = x
123 155
 
124
-        self._y = randint(0, world.height)
156
+        self._y = randint(0, world.height - 1)
125 157
         if y is not None:
126 158
             if y < 0 or y >= world.height:
127 159
                 raise ValueError('Invalid Y position %d' % y)

Loading…
Cancel
Save