123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307 |
- """ @brief This module contains stuff that represent Turmit's world
-
- @TODO add turmit score based on how many input (like x,y,r,g,b) encountered
- on __call__ : a score or a bool
-
- """
-
- import colorsys
- import scipy.misc
- import logging
- import time
- import numpy as np
- from random import randint
-
- from .turmit import Turmit
-
- logger = logging.getLogger()
-
- def eval_prog(args):
- ''' @brief Return an array of fractdim
- @param args : dirty argparser returned arguments
- '''
- startall = time.time()
- generation, progid, prog, trynum, args = args
- w = World(args.world_height, args.world_width, gray=True)
- logger.debug('Running P%d run#%d %s' % (progid, trynum, prog))
- w.raz()
- turmits = [LivingTurmit(world=w, prog=prog, diag=not args.no_diagonals)
- for _ in range(args.turmit_count)]
-
- start = time.time()
- if not args.mod_steps or trynum == 0:
- steps = args.steps
- else:
- steps = args.steps - int((args.steps / 1.5) // (args.repeat_eval - trynum))
- for step in range(steps):
- for turmit in turmits:
- turmit()
- stop = time.time()
-
- score_dir = sum([t.dirvar() for t in turmits]) / len(turmits)
- score_deter = sum([t.determinist() for t in turmits]) / len(turmits)
- score_fract = w.fractdim()
- score = (score_dir * score_fract) * (score_deter * 0.2 + 0.8)
- sinfo = {'F':score_fract, 'D':score_dir}
- #score = sum([score_fract] + [score_dir] * 4) / 5
- if not args.quiet:
- tall = time.time() - startall
- msg = 'G%d P%d R%d: %d steps scores %.3f:(D:%.3f,F:%.3f,Dtr:%.3f) in %.3fs (%dus per step)\t%s'
- msg %= (generation, progid, trynum, steps, score, score_dir,
- score_fract, score_deter, tall,
- ((stop - start)*1000000)//steps//len(turmits),
- str(prog))
- logger.info(msg)
- return progid, score, sinfo
-
- class World(object):
- ''' @brief A Turmit world. An array of dim == 2 with tuple(R,G,B) '''
-
- def __init__(self, x, y, gray=False):
- if x < 1:
- raise ValueError('Invalid value %d for x size')
- if y < 1:
- raise ValueError('Invalid value %d for x size')
- self._x = x
- self._y = y
- self._val = None
- self._gray = gray
- self.raz()
-
- @property
- def height(self):
- return self._y
-
- @property
- def width(self):
- return self._x
-
- def raz(self, color=None):
- ''' @brief Set all value to color
- @param color tuple | float : tuple(R,G,B) or greylevel
- '''
- if color is None:
- if self._gray:
- self._val = np.zeros([self._y, self._x])
- else:
- self._val = np.zeros([self._y, self._x, 3])
- elif color == 'rand':
- if self._gray:
- self._val = np.random.randint(255, size=(self._y, self._x))
- else:
- self._val = np.random.randint(255, size=(self._y, self._x, 3))
- else:
- self._val = [[color for _ in range(self._x)] for _ in range(self._y)]
- self._val = np.array(self._val)
-
- def save(self, filename):
- ''' @brief Save as an image '''
- scipy.misc.imsave(filename, self._val)
-
- def fractdim(self, threshold=None):
- ''' @return Minkowski–Bouligand dimension (computed) '''
- if not self._gray:
- Z = self.rgb2gray(self._val)
- else:
- Z = self._val
-
- # Only for 2d image
- assert(len(Z.shape) == 2)
-
- # From https://github.com/rougier/numpy-100 (#87)
- def boxcount(Z, k):
- S = np.add.reduceat(
- np.add.reduceat(Z, np.arange(0, Z.shape[0], k), axis=0),
- np.arange(0, Z.shape[1], k), axis=1)
-
- # We count non-empty (0) and non-full boxes (k*k)
- return len(np.where((S > 0) & (S < k*k))[0])
-
- if threshold is None:
- threshold = np.mean(Z)
- if threshold < 0.2:
- threshold = 0.2
-
- # Transform Z into a binary array
- Z = (Z < threshold)
-
- # Minimal dimension of image
- p = min(Z.shape)
-
- # Greatest power of 2 less than or equal to p
- n = 2**np.floor(np.log(p)/np.log(2))
-
- # Extract the exponent
- n = int(np.log(n)/np.log(2))
-
- # Build successive box sizes (from 2**n down to 2**1)
- sizes = 2**np.arange(n, 1, -1)
-
- # Actual box counting with decreasing size
- counts = []
- for size in sizes:
- counts.append(boxcount(Z, size))
-
- # Fit the successive log(sizes) with log (counts)
- coeffs = np.polyfit(np.log(sizes), np.log(counts), 1)
- return -coeffs[0]
-
- @staticmethod
- def rgb2gray(rgb):
- r, g, b = rgb[:,:,0], rgb[:,:,1], rgb[:,:,2]
- gray = 0.2989 * r + 0.5870 * g + 0.1140 * b
- return gray
-
- @staticmethod
- def single_rgb2gray(rgb):
- r,g,b = rgb
- gray = 0.2989 * r + 0.5870 * g + 0.1140 * b
- return gray
-
- def __getitem__(self, a):
- return self._val[a]
-
- def __setitem__(self, a, b):
- raise RuntimeError('No allowed')
-
-
- class LivingTurmit(Turmit):
- ''' @brief Represent a Turmit with coordinates in a world '''
-
- def __init__(self, world, color = None, x=None, y=None, diag=True, **kwargs):
- super().__init__(**kwargs)
-
- if not isinstance(world, World):
- msg = 'World argument expected to be World but %s found'
- raise TypeError(msg % type(world))
- if color is not None and len(color) != 3:
- msg = 'Color expected to be a tuple(R,G,B) but %s found'
- raise TypeError(msg % color)
- ## @brief If true direction contains diagonals
- self._diag = bool(diag)
- ## @brief Stores Turmit color
- rand_hue = randint(0,255) / 255
- rand_lvl = randint(127,255) / 255
- rand_val = randint(127,255) / 255
- self._col = tuple(round(i * 255)
- for i in colorsys.hsv_to_rgb(rand_hue,
- rand_lvl,
- rand_val))
- if world._gray == 'bw':
- self._col = 255.0
- elif world._gray:
- self._col = tuple(round(i * 255)
- for i in colorsys.hsv_to_rgb(rand_hue,1,1))
- self._col = World.single_rgb2gray(self._col)
- if color is not None:
- self._col = color
- ## @brief @ref World instance
- self._world = world
-
- self._x = randint(0, world.width - 1)
- if x is not None:
- if x < 0 or x >= world.width:
- raise ValueError('Invalid X position %d' % x)
- self._x = x
-
- self._y = randint(0, world.height - 1)
- if y is not None:
- if y < 0 or y >= world.height:
- raise ValueError('Invalid Y position %d' % y)
- self._y = y
-
- # Direction variation addon
- self._absx = self._absy = 0
- self._prev_abs = (0, 0)
- self._dirvar = 0
- self._dirsum = (0,0)
- self._prev_dirvar = None
- self._dirvar_res = 64
- self._dirvar_res_inc = 1
- self._steps = 5
- self._dirvar_cnt = self._dirvar_res
-
-
- def __call__(self):
- ''' @brief Exec a turmit and move it '''
- if self._world._gray:
- r = g = b = self._world[self._y][self._x]
- else:
- r,g,b = self._world[self._y][self._x]
- tdir = super().__call__(x=self._x, y=self._y, r=r, g=g, b=b)
- self.move(tdir)
-
- # direction variation handling
- #if self._steps % self._dirvar_res == 0:
- if self._dirvar_cnt <= 0:
- dy = self._absy - self._prev_abs[0]
- dx = self._absx - self._prev_abs[1]
- self._prev_abs = (self._absy, self._absx)
- if self._prev_dirvar is not None:
- pdvar = self._prev_dirvar
- var = abs(pdvar[0] - dy), abs(pdvar[1] - dx)
- var = [ v if v > 1 else 0 for v in var ]
- self._dirvar += sum(var)
- self._prev_dirvar = (dy, dx)
- self._dirvar_cnt = self._dirvar_res
- self._dirvar_res += self._dirvar_res_inc
-
- self._dirvar_cnt -= 1
- self._steps += 1
-
- def determinist(self):
- ''' @brief Process a determinit score : determinist_expr / total expr
- @return float in [0.0..1.0]
- '''
- return (self._determin_score + 1) / self._steps
-
- def dirvar(self):
- ''' @brief Process @ref _dirvar to return a direction variation
- score
- @return a score in [0..1]
- '''
- dvres = 1 + (self._dirvar / (self._steps * 2))
- return 1 - (1/(dvres**4))
-
- #return self._dirvar / (self._steps * 2)
-
- #d = self._dirvar
- #return sum([sum([abs(d[i][j] - d[i+1][j]) for j in (0,1)])
- # for i in range(len(d)-1)]) / (self._steps * 2)
-
-
- def move(self, tdir, setcol=True):
- ''' @brief Update coordinates given a direction
- @param tdir int : One of 8 directions (integer is understanded
- as tdir % 8
- @param setcol bool : If True update color of current position
- before leaving it
- @return y,x new position
- '''
- if setcol:
- self._world[self._y][self._x] = self._col
-
- tdir %= 8 if self._diag else 4
-
- if tdir == 0 or tdir == 4 or tdir == 5:
- self._absx += 1
- self._x += 1
- self._x %= self._world.width
- elif tdir == 2 or tdir == 7 or tdir == 6:
- self._absx -= 1
- if self._x == 0:
- self._x = self._world.width -1
- else:
- self._x -= 1
- if tdir == 1 or tdir == 4 or tdir == 6:
- self._absy += 1
- self._y += 1
- self._y %= self._world.height
- elif tdir == 3 or tdir == 7 or tdir == 5:
- self._absy -= 1
- if self._y == 0:
- self._y = self._world.height - 1
- else:
- self._y -= 1
- return self._y, self._x
|