Genetic Turmit Evolver
python
c
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

__main__.py 11KB

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