optimize.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772
  1. import sys
  2. import os
  3. import time
  4. from datetime import datetime
  5. from collections import namedtuple, defaultdict
  6. import logging
  7. from Queue import Empty
  8. import math
  9. from random import randrange, uniform, choice, random
  10. from pygene.gene import FloatGeneMax, FloatGeneExchange
  11. from pygene.organism import MendelOrganism
  12. from pygene.population import Population
  13. import numpy as np
  14. from runphoton import BBox, Resolution
  15. from utils.materials import loadMaterials, materialsAsXml, Brdf
  16. from utils.xml import FileToTree, prettyPrint, StringToTree, subelement, TreeToFile
  17. from utils.path import path
  18. from utils import daemonthread
  19. from utils import accelerated
  20. import simulate as simulator
  21. log = logging.getLogger('lws')
  22. DEVICE_ADAPTION = [-90, -85, -80, -75,-70, -65, -60, -55, -50, -45, -40,-30, -20, 0]
  23. DEVICE_ADAPTION = np.arange(-95, -20, 3.0)
  24. #~ DEVICE_ADAPTION = [-90, -80, -70, -60, -50, -40, -30, 0]
  25. DA_GENE_NAMES = ['da_%s' % -i for i in DEVICE_ADAPTION]
  26. # set via server.py - holds lws instance
  27. app = None
  28. class PowerGene(FloatGeneExchange):
  29. """
  30. Gene which represents the numbers used in our organism
  31. """
  32. # genes get randomly generated within this range
  33. # initialized in startOptimizeRun()
  34. randMin = 0
  35. randMax = 0
  36. # probability of mutation
  37. mutProb = 0.2
  38. # degree of mutation
  39. mutAmt = 0.2
  40. def mutate(self):
  41. """
  42. Mutate this gene's value by a random amount
  43. within the range, which is determined by
  44. multiplying self.mutAmt by the distance of the
  45. gene's current value from either endpoint of legal values
  46. perform mutation IN-PLACE, ie don't return mutated copy
  47. """
  48. if random() < 0.5:
  49. # mutate downwards:
  50. fact = 1.0 - uniform(0, self.mutAmt)
  51. newval = self.value * fact
  52. self.value = max(self.randMin, newval)
  53. else:
  54. # mutate upwards:
  55. fact = 1.0 + uniform(0, self.mutAmt)
  56. newval = self.value * fact
  57. self.value = min(self.randMax, newval)
  58. def randomValue(self):
  59. """
  60. Generates a plausible random value
  61. for this gene.
  62. """
  63. values = []
  64. v = self.randMin
  65. while v < self.randMax:
  66. values.append(v)
  67. v = v * 1.3
  68. return choice(values)
  69. class NormalizerGene(FloatGeneExchange):
  70. """
  71. Gene which represents the numbers used in our organism
  72. """
  73. # genes get randomly generated within this range
  74. randMin = 89.0
  75. randMax = 91.0
  76. # probability of mutation
  77. mutProb = 0.2
  78. # degree of mutation
  79. mutAmt = 0.2
  80. class DeviceAdaptionGene(FloatGeneExchange):
  81. # genes get randomly generated within this range
  82. randMin = -8.0
  83. randMax = 8.0
  84. mutProb = 0.1
  85. mutAmt = 0.1
  86. class MaterialGene(FloatGeneExchange):
  87. """
  88. Gene which represents the numbers used in our organism
  89. """
  90. # genes get randomly generated within this range
  91. randMin = 0.0
  92. randMax = 1.0
  93. # probability of mutation
  94. mutProb = 0.2
  95. # degree of mutation
  96. mutAmt = 0.1
  97. def optimizeDeviceAdaption(station2apid_locid2measurement, apid_locid2estimated, max_generations):
  98. stations = station2apid_locid2measurement.keys()
  99. shape = station2apid_locid2measurement.values()[0].shape
  100. class DeviceAdaptionConverger(MendelOrganism):
  101. """
  102. Implements the organism which tries
  103. to converge a function
  104. """
  105. genome = {'%s_%s' % (station, n): DeviceAdaptionGene for n in DA_GENE_NAMES for station in stations}
  106. def genes2values(self):
  107. da_values = {}
  108. for station in station2apid_locid2measurement:
  109. da_values[station] = [(DEVICE_ADAPTION[i], self['%s_%s' % (station, n)]) for i, n in enumerate(DA_GENE_NAMES)]
  110. return da_values
  111. def fitness(self):
  112. if self.fitness_cache is not None:
  113. return self.fitness_cache
  114. da_values = self.genes2values()
  115. self.used_values = da_values
  116. avg_delta = accelerated.compareForDeviceAdaption(da_values, estimated_3d, station2apid_locid2measurement)
  117. #~ deltas = []
  118. #~ for i in range(shape[0]):
  119. #~ for j in range(shape[1]):
  120. #~ res = a[i, j, 0] - self.apid_locid2measurement[i, j]
  121. #~ if np.isfinite(res):
  122. #~ deltas.append(abs(res))
  123. #~ avg_delta = sum(deltas) / len(deltas)
  124. fitness = avg_delta
  125. self.fitness_cache = fitness
  126. return fitness
  127. #~ print time.time() - t
  128. def __repr__(self):
  129. s = []
  130. for station in ['iconia']:
  131. davalues = [(DEVICE_ADAPTION[i], self['%s_%s' % (station, n)]) for i, n in enumerate(DA_GENE_NAMES)]
  132. s.append(station + ': ' + ', '.join('%s: %.1f' % e for e in davalues))
  133. return "<DeviceAdaptionConverger> avg-delta=%.2f\n%s" % (self.fitness_cache, '\n'.join(s))
  134. ## ======================
  135. # reshape to 3 dim array
  136. estimated_3d = apid_locid2estimated.reshape(shape[0], shape[1], 1).astype(np.float32)
  137. childcull = 50
  138. childcount = 100
  139. pop = Population(species=DeviceAdaptionConverger, init=300)
  140. t = time.time()
  141. #~ print DeviceAdaptionConverger().fitness()
  142. gen_idx = 0
  143. while gen_idx < max_generations:
  144. # execute a generation
  145. pop.gen(childcull, childcount)
  146. log.info('generation of genes: %s [population: %s] [fitness: %.2f]' % (gen_idx, len(pop.organisms), pop.best().fitness_cache))
  147. #~ if gen_idx-1 % 25 == 0:
  148. #~ log.info('%r' % pop.best())
  149. gen_idx += 1
  150. best = pop.best()
  151. return best.used_values, best.fitness()
  152. class RadioPropConverger(MendelOrganism):
  153. """
  154. Implements the organism which tries
  155. to converge a function
  156. """
  157. genome = dict()
  158. ORGANISM_COUNT = 0
  159. def __init__(self, *args, **kwargs):
  160. RadioPropConverger.ORGANISM_COUNT += 1
  161. self.organismcount = RadioPropConverger.ORGANISM_COUNT # our id
  162. MendelOrganism.__init__(self, *args, **kwargs)
  163. def prepare_fitness(self):
  164. if not app.optimizer.running:
  165. return
  166. if not getattr(self, 'prepared', False):
  167. self.runs = set()
  168. materials = {}
  169. for matname in app.optimizer.adjustable_materials:
  170. materials[matname] = Brdf(reflect=self['%s_reflect' % matname], alpha=self['%s_alpha' % matname])
  171. queued_ids = [] # only for logging
  172. for apid in app.optimizer.activeAPs:
  173. apcfg = app.config['aps'][apid]
  174. power = self['%s_power' % apcfg['powerid']]
  175. ap = simulator.AccessPoint(id=apid, x=apcfg['x'], y=apcfg['y'], z=apcfg['z'], power=power)
  176. density = app.optimizer.density
  177. bbox = app.optimizer.bbox
  178. resolution = app.optimizer.resolution
  179. scenename = app.optimizer.scene
  180. numphotons = app.optimizer.numphotons
  181. disabledObjects = app.optimizer.disabledObjects
  182. run = simulator.Run(scenename, app.activeScene['objfile'], materials, ap, bbox, resolution,
  183. density, numphotons, code='inspect_locids', locidinfo=app.known_locations,
  184. disabledObjects=disabledObjects)
  185. # if we use self.organismcount as priority, the organisms are evaluated pretty much sequentially
  186. # and that is good for parallelizablity
  187. priority = self.organismcount
  188. app.simulator.queue(run, priority=priority)
  189. self.runs.add(run)
  190. queued_ids.append(run.id)
  191. log.info('organism %s, queued %s runs: %s' % (self.organismcount, len(queued_ids), ','.join(queued_ids)))
  192. self.prepared = True
  193. def fitness_complete(self):
  194. remaining = len(self.runs - app.simulator.finished_jobs)
  195. return remaining == 0
  196. def fitness(self):
  197. if self.fitness_cache is not None:
  198. return self.fitness_cache
  199. self.prepare_fitness()
  200. while True:
  201. if not app.optimizer.running:
  202. #~ log.info('optimizer down, skipping fitness calculation')
  203. return 10**9 # return something really unfit
  204. if self.fitness_complete():
  205. break
  206. else:
  207. waiting, running, finished = app.simulator.queryRuns(simulator.Run.known)
  208. remaining = len(self.runs - finished)
  209. log.info('organism %s fitness calc: %s remaining [waiting: %s, running %s]' % (self.organismcount, remaining, len(waiting), len(running)))
  210. time.sleep(3.0)
  211. #~ da_values = {}
  212. #~ for station in app.optimizer.stations:
  213. #~ da_values[station] = [(DEVICE_ADAPTION[i], self['%s_%s' % (station, n)]) for i, n in enumerate(DA_GENE_NAMES)]
  214. #~ rssieq = self['rssi_eq']
  215. max_locid = max(app.known_locations.keys())
  216. apid_locid2estimated = np.zeros(shape=(len(app.optimizer.activeAPs), max_locid+1)) + np.inf
  217. for run in self.runs:
  218. apidx = app.optimizer.activeAPs.index(run.ap.id)
  219. for locid in app.known_locations.keys():
  220. estimated = run.locid2rssi[str(locid)]
  221. #~ print estimated
  222. apid_locid2estimated[apidx, locid] = estimated
  223. OPTIMIZE_DAV = False
  224. if OPTIMIZE_DAV:
  225. da_values, avg_delta_opt = optimizeDeviceAdaption(app.optimizer.station2measurements, apid_locid2estimated, 20)
  226. else:
  227. da_values = {}
  228. for station in app.optimizer.station2measurements:
  229. da_values[station] = []
  230. avg_delta_opt = None
  231. deltas = []
  232. values = defaultdict(list) # locid, measurements, estimated, delta for each ap
  233. for run in self.runs:
  234. apidx = app.optimizer.activeAPs.index(run.ap.id)
  235. for station in da_values:
  236. apid_locid2measurement = app.optimizer.station2measurements[station]
  237. for locid in app.known_locations.keys():
  238. measured = apid_locid2measurement[apidx, locid]
  239. # read simulated rss value for locid
  240. # that has been transfered from worker node via worker_code_inspect_locids.py
  241. estimated_raw = run.locid2rssi[str(locid)]
  242. if estimated_raw == 0:
  243. continue
  244. estimated = math.log(estimated_raw, 10) * 10
  245. normalized = accelerated.normalize(estimated_raw, da_values[station], isInDB=False)
  246. delta = abs(measured - normalized)
  247. if np.isnan(delta) or not np.isfinite(delta) or delta > 100:
  248. continue
  249. if not normalized > -100:
  250. continue
  251. deltas.append(delta)
  252. values[run.ap.id].append((station, locid, measured, estimated, normalized, delta))
  253. # all runs have the same material params
  254. # use the params of the last run
  255. materials = run.materials
  256. #~ res = run(x)
  257. avg_delta = np.sum(deltas) / float(len(deltas))
  258. if avg_delta_opt is not None:
  259. assert round(avg_delta_opt, 4) == round(avg_delta, 4), '%s != %s' % (avg_delta_opt, avg_delta)
  260. powers = [(g[:-6], self[g]) for g in self.genome.keys() if g.endswith('_power')]
  261. app.optimizer.newSimulationResult(self.organismcount, materials, powers, da_values, avg_delta, values)
  262. fitness = avg_delta
  263. self.fitness_cache = fitness
  264. return fitness
  265. def __repr__(self):
  266. return "organism %s avg-delta=%.2f" % (self.organismcount, self.fitness_cache if self.fitness_cache is not None else -1.0)
  267. #~ Iteration = namedtuple('Iteration', 'iter avg_delta paramxml path')
  268. #~ def loadData(REP_DIR):
  269. #~ dirs = os.listdir(REP_DIR)
  270. #~ def parse(name):
  271. #~ try:
  272. #~ return datetime.strptime(name, '%Y-%m-%dT%H_%M_%S')
  273. #~ except Exception:
  274. #~ return None
  275. #~ optruns = []
  276. #~ for name in dirs:
  277. #~ if parse(name) is None:
  278. #~ continue
  279. #~ subdirs = [os.path.join(REP_DIR, name, p) for p in os.listdir(os.path.join(REP_DIR, name))]
  280. #~ if len(subdirs) == 1:
  281. #~ session = os.path.split(subdirs[0])[-1]
  282. #~ bestdir = os.path.join(subdirs[0], 'best')
  283. #~ best_iterations = []
  284. #~ avg_delta = None
  285. #~ iter = 0
  286. #~ if os.path.exists(bestdir):
  287. #~ for n in os.listdir(bestdir):
  288. #~ iter = int(n.split('_')[0])
  289. #~ avg_delta = float(n.split('_')[1])
  290. #~ paramxml = open(os.path.join(bestdir, n, 'params.xml')).read()
  291. #~ best_iterations.append(Iteration(iter, avg_delta, paramxml, os.path.join(bestdir, n)))
  292. #~ lessThanOneDayOld = os.stat(subdirs[0]).st_mtime > time.time() - 3600 * 24 * 7
  293. #~ interesting = (avg_delta is not None and avg_delta < 4.0) or (lessThanOneDayOld and iter > 1)
  294. #~ if interesting:
  295. #~ optruns.append((name, parse(name), session, best_iterations))
  296. #~ # return sorted by ts ascending
  297. #~ return list(sorted(optruns, key=lambda x: x[1]))
  298. class Optimizer(object):
  299. def __init__(self):
  300. self.runner = None
  301. self.running = False
  302. self.startts = None
  303. self.timelimit = 0
  304. self.genlimit = 0
  305. self.sessiondir = None # directory for the current optsession with count: optsession_12345
  306. self.optsession = None # current optsession
  307. self.station = None # list of stations in string form
  308. self.stations = None # list of stations that should be included in optimize run
  309. self.gen_idx = 0 # current generation index
  310. self.min_avg_delta = 10**9 # best avg delta of current optsession (== best fitness)
  311. # optsession init params
  312. self.power = self.density = self.numphotons = self.resolution = self.bbox = self.scene = None
  313. self.tmpdir = app.config['tmp_optruns']
  314. if not self.tmpdir.exists():
  315. self.tmpdir.mkdir()
  316. # loaded measurements for each active station
  317. self.station2measurements = {}
  318. self.activeAPs = []
  319. daemonthread(target=self._checkTimelimit, name="optimizer_timelimit_check")
  320. def _checkTimelimit(self):
  321. while True:
  322. try:
  323. if self.running and self.genlimit > 0 and self.gen_idx > self.genlimit:
  324. log.warn('GENERATION LIMIT reached, stopping!')
  325. self.running = False
  326. elif (self.running and self.timelimit > 0 and
  327. time.time() - self.startts > self.timelimit):
  328. log.warn('TIMELIMIT reached, stopping!')
  329. self.running = False
  330. except Exception:
  331. log.exception('error during _checkTimelimit')
  332. try:
  333. time.sleep(1)
  334. except Exception:
  335. pass # ignore error during shutdown cleanup
  336. #~ if self.startts is not None:
  337. #~ log.info('TIMELIMIT: %s seconds remaining' % (self.timelimit - (time.time() - self.startts)))
  338. def loadPopulation(self, fname):
  339. tree = FileToTree(fname)
  340. #~ print 'load', fname
  341. print prettyPrint(tree)
  342. #~ tree.xpath()
  343. def newSimulationResult(self, orgid, materials, powers, da_values, avg_delta, deltavalues):
  344. minsfile = self.sessiondir.joinpath('mins.txt')
  345. minsfile.touch()
  346. allfile = self.sessiondir.joinpath('population_%s.txt' % self.gen_idx)
  347. allfile.touch()
  348. dt = datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
  349. def storeToFile(f):
  350. s = '%s gen:%s org:%s avg-delta:%.2f ' % (dt, self.gen_idx, orgid, avg_delta)
  351. for matname, brdf in sorted(materials.items()):
  352. s += '%s:%.3f,%.3f ' % (matname, brdf.reflect, brdf.alpha)
  353. for powid, power in powers:
  354. s += '%s:%.3e ' % (powid, power)
  355. #~ s += 'n/rssieq:%.3f ' % rssi_eq
  356. for station, dav in sorted(da_values.items()):
  357. for da_rssi, v in dav:
  358. s += 'da/%s_%s:%.1f ' % (station, da_rssi, v)
  359. open(f, 'a').write(s + '\n')
  360. storeToFile(allfile)
  361. if avg_delta < self.min_avg_delta:
  362. log.info('new minimum reached: %.2f' % avg_delta)
  363. storeToFile(minsfile)
  364. self.min_avg_delta = avg_delta
  365. minsdir = self.sessiondir.joinpath('mins')
  366. if not minsdir.exists():
  367. minsdir.mkdir()
  368. deltafile = minsdir.joinpath('deltas_%s_%s.txt' % (self.gen_idx, orgid))
  369. with deltafile.open('w') as f:
  370. for apid, values in sorted(deltavalues.items()):
  371. f.write('ap: %s\n' % apid)
  372. f.write('locid measured simulated normalized delta\n')
  373. for station, locid, measured, estimated, normalized, delta in values:
  374. f.write('%s %s %.1f %.1f %.1f %.1f\n' % (station, locid, measured, estimated, normalized, delta))
  375. def startOptimizeRun(self, optsession, station, power, density, numphotons,
  376. resolution, bbox, scene, startpop, childcount,
  377. childcull, mutprob, mutamt, resume=None, neworgs=0,
  378. timelimit=0, genlimit=0):
  379. if self.running:
  380. log.error('an optimizer run is still running')
  381. pass
  382. else:
  383. self.station = station
  384. self.stations = station.split(',')
  385. self.timelimit = int(timelimit)
  386. self.genlimit = int(genlimit)
  387. self.power = power
  388. self.density = density
  389. self.numphotons = numphotons
  390. self.resolution = resolution
  391. self.bbox = bbox
  392. self.scene = scene
  393. self.startpop = startpop
  394. self.childcount = childcount
  395. self.childcull = childcull
  396. # setup PowerGene
  397. PowerGene.randMin = power * 0.05 # 1/10 of initial power = lower bound
  398. PowerGene.randMax = power * 50.0 # 10 times initial power = upper bound
  399. PowerGene.mutProb = mutprob
  400. PowerGene.mutAmt = mutamt
  401. MaterialGene.mutProb = mutprob
  402. MaterialGene.mutAmt = mutamt
  403. sesscount = max([0] + [int(d.name.split('_')[1]) for d in self.tmpdir.dirs() if d.name.split('_')[0] == optsession])
  404. self.optsession = '%s_%s' % (optsession, sesscount+1)
  405. self.sessiondir = self.tmpdir.joinpath(self.optsession)
  406. self.sessiondir.mkdir()
  407. ## HACKISH for eval
  408. self.adjustable_materials = ('MatConcrete', 'MatLightWalls')
  409. self.adjustable_materials = ('MatConcrete',)
  410. self.adjustable_materials = app.activeScene['materials'].scalars
  411. # [so.name for so in self.scene_objects if not ('light' in so.name.lower() or 'concrete' in so.name.lower())]
  412. self.disabledObjects = ['Iron_Doors_ID1', 'Fascade_OG2_M6.002', 'Cupboards_Plane.002', 'Fascade_EG_M6.001',
  413. 'Glass_Doors_Plane', 'Fascade_OG1_M6', 'Railing_Plane.Railing', 'Glass_Windows_OG1_GW01', 'Normal_Doors_ND1',
  414. 'Glass_Windows_OG2_GW01.002', 'Glass_Windows_EG1_GW01.001', 'Stairs_Plane.Stairs', 'Tables_Plane.004',
  415. 'Hardware_Plane.001']
  416. self.disabledObjects = []
  417. # see also simulator remapMaterials
  418. initfile = self.sessiondir.joinpath('init.txt')
  419. s = 'session: %s\n' % optsession
  420. s += 'timelimit: %s\n' % timelimit
  421. s += 'genlimit: %s\n' % genlimit
  422. s += 'station: %s\n' % station
  423. s += 'power: %.1e\n' % power
  424. s += 'density: %.3f\n' % density
  425. s += 'numphotons: %s\n' % numphotons
  426. s += 'resolution: %s,%s,%s\n' % resolution
  427. s += 'bbox: %s,%s,%s,%s,%s,%s\n' % bbox
  428. s += 'scene: %s\n' % scene
  429. s += 'startpop: %s\n' % startpop
  430. s += 'childcount: %s\n' % childcount
  431. s += 'childcull: %s\n' % childcull
  432. s += 'mutprob: %s\n' % mutprob
  433. s += 'mutamt: %s\n' % mutamt
  434. initfile.write_text(s)
  435. apids = [apid for apid in app.config['aps'] if app.config['aps'][apid]['optimize']]
  436. self.activeAPs[:] = apids
  437. self._setupGenes()
  438. if resume:
  439. d = self.tmpdir.joinpath(resume)
  440. if not d.exists():
  441. raise RuntimeError('no session %s available' % resume)
  442. popfiles = d.files('population_*.xml')
  443. if len(popfiles) == 0:
  444. raise RuntimeError('no population for session %s available' % resume)
  445. popfiles.sort(key=lambda f: f.mtime)
  446. organisms = self.xml2population(FileToTree(popfiles[-1]))
  447. for i in range(neworgs):
  448. organisms.append(RadioPropConverger())
  449. else:
  450. organisms = None
  451. self.min_avg_delta = 10**9
  452. self.running = True
  453. self.startts = time.time()
  454. self.runner = daemonthread(target=self._optimizeRun, name="optimizeRun", args=(organisms, ))
  455. def stopOptimizeRun(self):
  456. if self.running:
  457. self.running = False
  458. app.simulator.finishAll()
  459. def xml2population(self, tree):
  460. self._setupGenes()
  461. organisms = []
  462. for orgEl in tree.xpath('//organism'):
  463. genes = {}
  464. for geneEl in orgEl.xpath('genepair'):
  465. # get class from current module
  466. cls = globals()[geneEl.attrib['class']]
  467. g1 = float(geneEl.attrib['g1'])
  468. g2 = float(geneEl.attrib['g2'])
  469. genes[geneEl.attrib['name']] = (cls(g1), cls(g2))
  470. organism = RadioPropConverger(**genes)
  471. organisms.append(organism)
  472. return organisms
  473. def population2xml(self, pop, **kwds):
  474. tree = StringToTree('<population/>')
  475. for k, v in kwds.items():
  476. tree.getroot().attrib[k] = str(v)
  477. unique_genes = set()
  478. for org in pop.organisms:
  479. orgEl = subelement(tree.getroot(), 'organism', attribs={'fitness': org.fitness(), 'count': org.organismcount})
  480. for name, cls in org.genome.items():
  481. pairEl = subelement(orgEl, "genepair", attribs={'name': name, 'class': cls.__name__,})
  482. pair = org.genes[name]
  483. pairEl.attrib['g1'] = str(pair[0].value)
  484. pairEl.attrib['g2'] = str(pair[1].value)
  485. unique_genes.add(cls)
  486. for cls in unique_genes:
  487. attribs = {'class': cls.__name__, 'mutamt': cls.mutAmt,
  488. 'mutprob': cls.mutProb, 'min': cls.randMin, 'max': cls.randMax}
  489. subelement(tree.getroot(), 'genespec', attribs=attribs)
  490. return tree
  491. def _setupGenes(self):
  492. aps = app.config['aps']
  493. powids = {aps[apid]['powerid'] for apid in self.activeAPs}
  494. RadioPropConverger.genome.clear()
  495. RadioPropConverger.genome.update(dict(
  496. [('%s_reflect' % matname, MaterialGene) for matname in self.adjustable_materials] +
  497. [('%s_alpha' % matname, MaterialGene) for matname in self.adjustable_materials] +
  498. [('%s_power' % powid, PowerGene) for powid in powids]
  499. ))
  500. def _loadMeasurements(self):
  501. self.station2measurements.clear()
  502. max_locid = max(app.known_locations.keys())
  503. for station in self.stations:
  504. # initialize with infinity
  505. a = self.station2measurements[station] = np.zeros(shape=(len(self.activeAPs), max_locid+1)) + np.inf
  506. locid2measurecount = defaultdict(int)
  507. remainingActive = []
  508. for i, apid in enumerate(self.activeAPs):
  509. locid2measurement = app.getMeasurements(station, apid)
  510. if locid2measurement is not None:
  511. #~ locid2measurement_overlay = app.getMeasurements(station, apid, fromMeasurementStore=False)
  512. for locid, (x, y, z, measured, all_rssis) in locid2measurement.items():
  513. a[i, locid] = float(measured)
  514. locid2measurecount[locid] += 1
  515. # overwrite with values from tracked paths
  516. #~ if locid in locid2measurement_overlay:
  517. #~ x, y, z, measured, all_rssis = locid2measurement_overlay[locid]
  518. #~ a[i, locid] = float(measured)
  519. remainingActive.append(apid)
  520. ### reduce to set of location with the
  521. N = 50
  522. sorted_locs = sorted(locid2measurecount.items(), key=lambda e: e[1])
  523. for l, c in sorted_locs:
  524. print l, c
  525. topNlocids = set([locid for locid, count in sorted_locs][-N:])
  526. for i, apid in enumerate(self.activeAPs):
  527. locid2measurement = app.getMeasurements(station, apid)
  528. if locid2measurement is not None:
  529. for locid, (x, y, z, measured, all_rssis) in locid2measurement.items():
  530. if locid not in topNlocids: # clear measurement by setting to inf
  531. a[i, locid] = np.inf
  532. log.info('%s of %s remaining active aps due to available measurements' % (len(remainingActive), i))
  533. self.activeAPs[:] = remainingActive
  534. def _optimizeRun(self, startorganisms):
  535. try:
  536. self._loadMeasurements()
  537. if startorganisms is not None:
  538. pop = Population(*startorganisms, init=self.startpop)
  539. else:
  540. pop = Population(species=RadioPropConverger, init=self.startpop)
  541. for org in pop.organisms:
  542. org.prepare_fitness()
  543. self.gen_idx = 0
  544. while self.running:
  545. log.info('generation of genes: %s [population: %s]' % (self.gen_idx, len(pop.organisms)))
  546. # execute a generation
  547. pop.gen(self.childcull, self.childcount)
  548. f = self.tmpdir.joinpath(self.sessiondir, 'population_%s.xml' % self.gen_idx)
  549. tree = self.population2xml(pop, init=self.startpop, childCount=self.childcount, childCull=self.childcull)
  550. TreeToFile(tree, f)
  551. self.gen_idx += 1
  552. except Exception:
  553. log.exception('error during optrun')
  554. finally:
  555. self.running = False
  556. def getDeviceAdaptionFromOptrun(self, optsession, device):
  557. f = self.tmpdir.joinpath(optsession, 'mins.txt')
  558. davalues = defaultdict(list)
  559. if not f.exists():
  560. log.error('got no mins for optrun %s - empty davalues' % optsession)
  561. return davalues
  562. # take last minimum
  563. mindata = f.text().strip().split('\n')[-1].split()
  564. for s in mindata[1:]:
  565. name, value = s.split(':')
  566. if name.startswith('da/'):
  567. device_darssi = name[3:].split('_')
  568. if len(device_darssi) == 1:
  569. # backward compat
  570. davalues[device].append((float(device_darssi[0]), float(value)))
  571. else:
  572. dev, darssi = device_darssi
  573. davalues[dev].append((float(darssi), float(value)))
  574. return davalues