Genetic Turmit Evolver
python
c
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

__main__.py 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. import sys
  2. import os
  3. import os.path
  4. import argparse
  5. import logging
  6. import time
  7. import datetime
  8. import numpy as np
  9. from random import randint
  10. import multiprocessing
  11. from multiprocessing import Pool, Process, Pipe
  12. logging.basicConfig(level=logging.INFO,
  13. format="%(created)d %(asctime)s:%(module)s(%(lineno)d):%(levelname)s:%(message)s")
  14. logger = logging.getLogger()
  15. logger.debug('Logger started')
  16. from . import turmit, rpnlib
  17. from .turmit import Turmit
  18. from .world import World, LivingTurmit, eval_prog
  19. from .mutator import mutate
  20. parser = argparse.ArgumentParser(description='Genetic Turmit Evolver')
  21. subparsers = parser.add_subparsers(help='sub-commands help')
  22. parser_evolve = subparsers.add_parser('evolve', help='evolving help')
  23. parser_evolve.add_argument('--steps', '-s', type=int, metavar='N',
  24. default=30000, help="Number of steps")
  25. parser_evolve.add_argument('--repeat-eval', '-R', type=int, metavar='N',
  26. default=5)
  27. parser_evolve.add_argument('--world-height', '-y', type=int, metavar='HEIGHT',
  28. default=512)
  29. parser_evolve.add_argument('--world-width', '-x', type=int, metavar='WIDTH',
  30. default=512)
  31. parser_evolve.add_argument('--turmit-count', '-T', type=int, metavar='N',
  32. default=3)
  33. parser_evolve.add_argument('--pool-size', '-p', type=int, metavar='N',
  34. default=10)
  35. parser_evolve.add_argument('--pool-div', '-D', type=int, metavar='N',
  36. default=2,
  37. help='Each generation keep 1/N of the pool')
  38. parser_evolve.add_argument('--max', '-m', action='store_const',
  39. default=False, const=True,
  40. help="Keep maximum score instead of average")
  41. parser_evolve.add_argument('--prog-size', type=int, metavar='SIZE',
  42. default=5)
  43. parser_evolve.add_argument('--prog', '-P', type=str, metavar='EXPR',
  44. default=None)
  45. parser_evolve.add_argument('--threads', type=int, metavar='N',
  46. default=os.cpu_count())
  47. parser_evolve.add_argument('--max-generation', '-G', type=int, metavar='N',
  48. default=0)
  49. parser_evolve.add_argument('--log-progs', '-L', type=str, metavar='FILENAME',
  50. default=None)
  51. parser_evolve.add_argument('--exp-mutate', '-E', action='store_const',
  52. default=False, const=True,
  53. help="If True make 2**copy_num mutation")
  54. parser_evolve.add_argument('--mod-steps', '-M', action='store_const',
  55. default=False, const=True,
  56. help="If True make steps change given try number")
  57. parser_evolve.add_argument('--quiet', '-q', action='store_const',
  58. default=False, const=True)
  59. parser_evolve.add_argument('--no-diagonals', '-d', action='store_const',
  60. default=False, const=True)
  61. parser_evolve.add_argument('--target', '-t', type=float, metavar="FLOAT",
  62. default=None,
  63. help="0 < Float <= 2.0 for targeted score")
  64. parser_gen = subparsers.add_parser('generate', help='evolving help')
  65. parser_gen.add_argument('--prog', '-P', type=str, default=None)
  66. parser_gen.add_argument('--output', '-o', type=str, default='gte.png')
  67. parser_gen.add_argument('--output-dir', '-A', type=str, default=None,
  68. help="Save images for animation in this directory")
  69. parser_gen.add_argument('--anim-div', '-D', type=int, default=2,
  70. help='Save all X images (default 2) when -O given')
  71. parser_gen.add_argument('--anim-fmt', '-F', type=str, default='GTE_ANIM_%d.png',
  72. help='Save all X images (default "GTE_ANIM_%%d.png") when -O given')
  73. parser_gen.add_argument('--world-height', '-y', type=int, metavar='HEIGHT',
  74. default=512)
  75. parser_gen.add_argument('--world-width', '-x', type=int, metavar='WIDTH',
  76. default=512)
  77. parser_gen.add_argument('--turmit-count', '-T', type=int, metavar='N',
  78. default=3)
  79. parser_gen.add_argument('--steps', '-s', type=int, metavar='N',
  80. default=30000)
  81. parser_gen.add_argument('--gray', '-G', action='store_const',
  82. default=False, const=True)
  83. parser_gen.add_argument('--no-diagonals', '-d', action='store_const',
  84. default=False, const=True)
  85. parser_gen.add_argument('--threads', '-t', type=int, metavar='N',
  86. default=os.cpu_count()-1,
  87. help="Animation writting threads")
  88. args = parser.parse_args()
  89. if 'pool_size' in args:
  90. # Evolver
  91. if args.prog is None:
  92. prog = rpnlib.RpnExpr.random(args.prog_size)
  93. progs = [mutate(prog, force=True) for _ in range(args.pool_size-1)]
  94. #progs = [rpnlib.RpnExpr.random(args.prog_size)
  95. # for _ in range(args.pool_size)]
  96. else:
  97. prog = rpnlib.RpnExpr.from_string(args.prog)
  98. if len(prog) == 0:
  99. prog = rpnlib.RpnExpr([rpnlib.RpnSymbol.from_string('0')])
  100. progs = [prog]
  101. progs += [mutate(prog, force=True) for _ in range(args.pool_size-1)]
  102. generation = 0
  103. pool = Pool(args.threads)
  104. if args.log_progs is not None:
  105. logprogs = open(args.log_progs, 'a')
  106. logprogs.write('%s Run args %s\n' % (datetime.datetime.now(),
  107. sys.argv))
  108. logprogs.write('%s\n' % args)
  109. logprogs.close()
  110. else:
  111. logger.warning('No log specified (--log-progs or -L)')
  112. while True:
  113. if args.max_generation > 0 and generation >= args.max_generation:
  114. exit(0)
  115. scores = []
  116. msg = 'Gen#%d Running %d eval with %d turmits and %d steps for each %d progs'
  117. logger.info(msg % (generation, args.repeat_eval, args.turmit_count,
  118. args.steps, args.pool_size))
  119. genstart = time.time()
  120. # Preparing work
  121. works = []
  122. for pid, prog in enumerate(progs):
  123. for i in range(args.repeat_eval):
  124. works.append((generation, pid, prog, i, args))
  125. # running jobs
  126. res = pool.imap(eval_prog, works)
  127. # Processing results
  128. if args.max:
  129. scores = [ [] for _ in range(len(progs))]
  130. for pid, score in res:
  131. scores[pid].append(score)
  132. scores = [(max(scores[i]), progs[i]) for i in range(len(progs))]
  133. else:
  134. scores = [0 for _ in range(len(progs))]
  135. sinfos = [dict() for _ in range(len(progs))]
  136. for pid, score, sinfo in res:
  137. scores[pid] += score
  138. # sum all score infos
  139. for k, s in sinfo.items():
  140. if k not in sinfos[pid]:
  141. sinfos[pid][k] = 0
  142. sinfos[pid][k] += s
  143. # avg all infos
  144. for i in range(len(sinfos)):
  145. sinfo = sinfos[i]
  146. for k in sinfo:
  147. sinfo[k] /= args.repeat_eval
  148. scores[i] /= args.repeat_eval
  149. scores = [(scores[i], progs[i], sinfos[i])
  150. for i in range(len(progs))]
  151. genstop = time.time()
  152. # Displaying eval results
  153. logger.info('Generation evaluating ended in %.2fs' % (genstop - genstart))
  154. if args.target is None:
  155. scores = sorted(scores, key=lambda x: x[0], reverse=True)
  156. else:
  157. scores = sorted(scores, key=lambda x: abs(x[0] - args.target),)
  158. for i, (score, prog, sinfo) in enumerate(scores):
  159. logger.info('P%d %.3f:(D:%.3f,F:%.3f) : "%s"' % (i, score,
  160. sinfo['D'],
  161. sinfo['F'],
  162. str(prog)))
  163. if args.log_progs is not None:
  164. with open(args.log_progs, 'a') as logprogs:
  165. for i, (score, prog, sinfo) in enumerate(scores):
  166. msg = 'Gen #%d P%d %.3f(D:%.3f,F:%.3f): "%s"\n'
  167. logprogs.write(msg % (generation, i, score, sinfo['D'],
  168. sinfo['F'], str(prog)))
  169. logprogs.flush()
  170. logprogs.write('\n')
  171. # Split pool & mutate the one we kept
  172. keep_div = args.pool_div
  173. progs = []
  174. for prog in [ prog for _, prog, _ in scores[:len(scores)//keep_div]]:
  175. for p2 in progs:
  176. if prog == p2:
  177. prog = mutate(prog, force=True)
  178. break
  179. progs.append(prog)
  180. for cur_mut in range(keep_div - 1):
  181. if args.exp_mutate:
  182. progs.append(mutate(prog, force=True, mutcount=2**cur_mut))
  183. else:
  184. progs.append(mutate(prog, force=True, mutcount=1))
  185. if len(progs) >= args.pool_size:
  186. break
  187. if len(progs) >= args.pool_size:
  188. break
  189. for pid, prog in enumerate(progs):
  190. logger.debug('P%d : %s' % (pid, str(prog)))
  191. generation += 1
  192. exit(0)
  193. else:
  194. # Generate
  195. if args.output_dir is not None:
  196. from PIL import Image
  197. def save_pool(im, fname):
  198. st1 = time.time()
  199. im.save(fname)
  200. logger.debug('Image %s saved in %.3fs' % (fname,
  201. time.time() - st1))
  202. write_pool = Pool(args.threads)
  203. async_res = []
  204. img_count = 0
  205. w = World(args.world_height, args.world_width, gray=args.gray)
  206. prog = rpnlib.RpnExpr.from_string(args.prog)
  207. turmits = [LivingTurmit(world=w, prog=prog, diag=not args.no_diagonals)
  208. for _ in range(args.turmit_count)]
  209. msg = 'Generating image for program %s with %d steps and %d turmits'
  210. logger.info(msg % (str(prog), args.steps, args.turmit_count))
  211. start = time.time()
  212. for step in range(args.steps):
  213. if step % 512 == 1:
  214. msg = 'Step %d/%d %dus/step fractdim=%.3f'
  215. msg %= (step, args.steps,
  216. ((time.time() - start)*1000000)//step//len(turmits),
  217. w.fractdim())
  218. logger.info(msg)
  219. for turmit in turmits:
  220. turmit()
  221. if args.output_dir is not None and (args.anim_div <= 1 or
  222. step % (args.anim_div-1) == 0):
  223. imgid = step // (1 if args.anim_div <= 1 else args.anim_div)
  224. img_count += 1
  225. fname = args.anim_fmt % imgid
  226. fname = os.path.join(args.output_dir, fname)
  227. im = Image.fromarray(np.uint8(w._val))
  228. async_res.append(write_pool.apply_async(save_pool, (im, fname)))
  229. while async_res[0].ready():
  230. async_res[0].get()
  231. async_res.pop(0)
  232. if args.output_dir is not None:
  233. while async_res[0].ready():
  234. async_res[0].get()
  235. sync_res.pop(0)
  236. write_pool.close()
  237. logger.info('Waiting for animation to be written on disk')
  238. img_st1 = None
  239. while len(async_res) > 0:
  240. async_res[0].get()
  241. async_res.pop(0)
  242. stat_upd = 100
  243. if len(async_res) % stat_upd == 0:
  244. msg = '%d images left' % (len(async_res))
  245. stupd = stat_upd
  246. if img_st1 is None:
  247. img_st1 = start
  248. stupd = img_count - len(async_res)
  249. img_st2 = time.time()
  250. img_st = (img_st2 - img_st1) / stupd
  251. img_ts = stupd / (img_st2 - img_st1)
  252. etcs = ''
  253. if img_st1 != start:
  254. etc = len(async_res) * img_st
  255. etcs = ' ETC:'
  256. if etc >= 60:
  257. etcs += '%d min ' % (etc // 60)
  258. etc %= 60
  259. etcs += '%ds' % etc
  260. msg += ' %.1fimg/s%s' % (img_ts, etcs)
  261. img_st1 = img_st2
  262. logger.info(msg)
  263. write_pool.join()
  264. #for _ in wps:
  265. # data_q.put((None, None))
  266. #for wp in wps:
  267. # wp.join()
  268. #data_q.close()
  269. logger.info('Animation written in "%s"' % args.output_dir)
  270. stop = time.time()
  271. score_dir = sum([t.dirvar() for t in turmits]) / len(turmits)
  272. fractdim = w.fractdim()
  273. msg = 'Score %.3f DirVar %.3f Fractdim %.3f after %d steps in %.2fs (%dus per step)'
  274. msg %= (score_dir * fractdim, score_dir, fractdim, args.steps,
  275. stop - start,
  276. ((stop - start)*1000000)//args.steps//args.turmit_count)
  277. logger.info(msg)
  278. w.save(args.output)
  279. logger.info('Image saved in %s' % args.output)
  280. exit(0)