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.

474 lines
19 KiB

6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
  1. import sys
  2. import operator
  3. from os import listdir, system
  4. import subprocess
  5. import re
  6. from collections import defaultdict
  7. from random import randrange
  8. from ale_py import ALEInterface, SDL_SUPPORT, Action
  9. from PIL import Image
  10. from matplotlib import pyplot as plt
  11. import cv2
  12. import pickle
  13. import queue
  14. from dataclasses import dataclass, field
  15. from sklearn.cluster import KMeans, DBSCAN
  16. from enum import Enum
  17. from copy import deepcopy
  18. import numpy as np
  19. import logging
  20. logger = logging.getLogger(__name__)
  21. #import readchar
  22. from sample_factory.algo.utils.tensor_dict import TensorDict
  23. from query_sample_factory_checkpoint import SampleFactoryNNQueryWrapper
  24. import time
  25. tempest_binary = "/home/spranger/projects/tempest-devel/ranking_release/bin/storm"
  26. rom_file = "/home/spranger/research/Skiing/env/lib/python3.10/site-packages/AutoROM/roms/skiing.bin"
  27. def tic():
  28. import time
  29. global startTime_for_tictoc
  30. startTime_for_tictoc = time.time()
  31. def toc():
  32. import time
  33. if 'startTime_for_tictoc' in globals():
  34. return time.time() - startTime_for_tictoc
  35. class Verdict(Enum):
  36. INCONCLUSIVE = 1
  37. GOOD = 2
  38. BAD = 3
  39. verdict_to_color_map = {Verdict.BAD: "200,0,0", Verdict.INCONCLUSIVE: "40,40,200", Verdict.GOOD: "00,200,100"}
  40. def convert(tuples):
  41. return dict(tuples)
  42. @dataclass(frozen=True)
  43. class State:
  44. x: int
  45. y: int
  46. ski_position: int
  47. velocity: int
  48. def default_value():
  49. return {'action' : None, 'choiceValue' : None}
  50. @dataclass(frozen=True)
  51. class StateValue:
  52. ranking: float
  53. choices: dict = field(default_factory=default_value)
  54. def exec(command,verbose=True):
  55. if verbose: print(f"Executing {command}")
  56. system(f"echo {command} >> list_of_exec")
  57. return system(command)
  58. num_tests_per_cluster = 50
  59. factor_tests_per_cluster = 0.2
  60. num_ski_positions = 8
  61. num_velocities = 8
  62. def input_to_action(char):
  63. if char == "0":
  64. return Action.NOOP
  65. if char == "1":
  66. return Action.RIGHT
  67. if char == "2":
  68. return Action.LEFT
  69. if char == "3":
  70. return "reset"
  71. if char == "4":
  72. return "set_x"
  73. if char == "5":
  74. return "set_vel"
  75. if char in ["w", "a", "s", "d"]:
  76. return char
  77. def saveObservations(observations, verdict, testDir):
  78. testDir = f"images/testing_{experiment_id}/{verdict.name}_{testDir}_{len(observations)}"
  79. if len(observations) < 20:
  80. logger.warn(f"Potentially spurious test case for {testDir}")
  81. testDir = f"{testDir}_pot_spurious"
  82. exec(f"mkdir {testDir}", verbose=False)
  83. for i, obs in enumerate(observations):
  84. img = Image.fromarray(obs)
  85. img.save(f"{testDir}/{i:003}.png")
  86. ski_position_counter = {1: (Action.LEFT, 40), 2: (Action.LEFT, 35), 3: (Action.LEFT, 30), 4: (Action.LEFT, 10), 5: (Action.NOOP, 1), 6: (Action.RIGHT, 10), 7: (Action.RIGHT, 30), 8: (Action.RIGHT, 40) }
  87. def run_single_test(ale, nn_wrapper, x,y,ski_position, velocity, duration=50):
  88. #def run_single_test(ale, nn_wrapper, x,y,ski_position, duration=50):
  89. #print(f"Running Test from x: {x:04}, y: {y:04}, ski_position: {ski_position}", end="")
  90. testDir = f"{x}_{y}_{ski_position}_{velocity}"
  91. #testDir = f"{x}_{y}_{ski_position}"
  92. for i, r in enumerate(ramDICT[y]):
  93. ale.setRAM(i,r)
  94. ski_position_setting = ski_position_counter[ski_position]
  95. for i in range(0,ski_position_setting[1]):
  96. ale.act(ski_position_setting[0])
  97. ale.setRAM(14,0)
  98. ale.setRAM(25,x)
  99. ale.setRAM(14,180) # TODO
  100. all_obs = list()
  101. speed_list = list()
  102. resized_obs = cv2.resize(ale.getScreenGrayscale(), (84,84), interpolation=cv2.INTER_AREA)
  103. for i in range(0,4):
  104. all_obs.append(resized_obs)
  105. for i in range(0,duration-4):
  106. resized_obs = cv2.resize(ale.getScreenGrayscale(), (84,84), interpolation=cv2.INTER_AREA)
  107. all_obs.append(resized_obs)
  108. if i % 4 == 0:
  109. stack_tensor = TensorDict({"obs": np.array(all_obs[-4:])})
  110. action = nn_wrapper.query(stack_tensor)
  111. ale.act(input_to_action(str(action)))
  112. else:
  113. ale.act(input_to_action(str(action)))
  114. speed_list.append(ale.getRAM()[14])
  115. if len(speed_list) > 15 and sum(speed_list[-6:-1]) == 0:
  116. saveObservations(all_obs, Verdict.BAD, testDir)
  117. return Verdict.BAD
  118. saveObservations(all_obs, Verdict.GOOD, testDir)
  119. return Verdict.GOOD
  120. def computeStateRanking(mdp_file, iteration):
  121. logger.info("Computing state ranking")
  122. tic()
  123. try:
  124. command = f"{tempest_binary} --prism {mdp_file} --buildchoicelab --buildstateval --build-all-labels --prop 'Rmax=? [C <= 1000]'"
  125. result = subprocess.run(command, shell=True, check=True)
  126. print(result)
  127. except Exception as e:
  128. print(e)
  129. sys.exit(-1)
  130. exec(f"mv action_ranking action_ranking_{iteration:03}")
  131. logger.info(f"Computing state ranking - DONE: took {toc()} seconds")
  132. def fillStateRanking(file_name, match=""):
  133. logger.info(f"Parsing state ranking, {file_name}")
  134. tic()
  135. state_ranking = dict()
  136. try:
  137. with open(file_name, "r") as f:
  138. file_content = f.readlines()
  139. for line in file_content:
  140. if not "move=0" in line: continue
  141. ranking_value = float(re.search(r"Value:([+-]?(\d*\.\d+)|\d+)", line)[0].replace("Value:",""))
  142. if ranking_value <= 0.1:
  143. continue
  144. stateMapping = convert(re.findall(r"([a-zA-Z_]*[a-zA-Z])=(\d+)?", line))
  145. #print("stateMapping", stateMapping)
  146. choices = convert(re.findall(r"[a-zA-Z_]*(left|right|noop)[a-zA-Z_]*:(-?\d+\.?\d*)", line))
  147. choices = {key:float(value) for (key,value) in choices.items()}
  148. #print("choices", choices)
  149. #print("ranking_value", ranking_value)
  150. state = State(int(stateMapping["x"]), int(stateMapping["y"]), int(stateMapping["ski_position"]), int(stateMapping["velocity"])//2)
  151. #state = State(int(stateMapping["x"]), int(stateMapping["y"]), int(stateMapping["ski_position"]))
  152. value = StateValue(ranking_value, choices)
  153. state_ranking[state] = value
  154. logger.info(f"Parsing state ranking - DONE: took {toc()} seconds")
  155. return state_ranking
  156. except EnvironmentError:
  157. print("Ranking file not available. Exiting.")
  158. toc()
  159. sys.exit(-1)
  160. except:
  161. toc()
  162. def createDisjunction(formulas):
  163. return " | ".join(formulas)
  164. def clusterFormula(cluster):
  165. if len(cluster) == 0: return
  166. formulas = list()
  167. for state in cluster:
  168. formulas.append(f"(x={state[0].x} & y={state[0].y} & velocity={state[0].velocity} & ski_position={state[0].ski_position})")
  169. while len(formulas) > 1:
  170. formulas_tmp = [f"({formulas[i]} | {formulas[i+1]})" for i in range(0,len(formulas)//2)]
  171. if len(formulas) % 2 == 1:
  172. formulas_tmp.append(formulas[-1])
  173. formulas = formulas_tmp
  174. return "(" + formulas[0] + ")"
  175. def clusterFormulaTrimmed(cluster):
  176. formula = ""
  177. states = [(s[0].x,s[0].y, s[0].ski_position, s[0].velocity) for s in cluster]
  178. #states = [(s[0].x,s[0].y, s[0].ski_position) for s in cluster]
  179. skiPositionGroup = defaultdict(list)
  180. for item in states:
  181. skiPositionGroup[item[2]].append(item)
  182. #todo add velocity here
  183. firstVelocity = True
  184. for skiPosition, skiPos_group in skiPositionGroup.items():
  185. formula += f"ski_position={skiPosition} & "
  186. velocityGroup = defaultdict(list)
  187. for item in skiPos_group:
  188. velocityGroup[item[3]].append(item)
  189. for velocity, velocity_group in velocityGroup.items():
  190. if firstVelocity:
  191. firstVelocity = False
  192. else:
  193. formula += " | "
  194. formula += f" (velocity={velocity} & "
  195. firstY = True
  196. yPosGroup = defaultdict(list)
  197. for item in velocity_group:
  198. yPosGroup[item[1]].append(item)
  199. for y, y_group in yPosGroup.items():
  200. if firstY:
  201. firstY = False
  202. else:
  203. formula += " | "
  204. sorted_y_group = sorted(y_group, key=lambda s: s[0])
  205. formula += f"( y={y} & ("
  206. current_x_min = sorted_y_group[0][0]
  207. current_x = sorted_y_group[0][0]
  208. x_ranges = list()
  209. for state in sorted_y_group[1:-1]:
  210. if state[0] - current_x == 1:
  211. current_x = state[0]
  212. else:
  213. x_ranges.append(f" ({current_x_min}<= x & x<={current_x})")
  214. current_x_min = state[0]
  215. current_x = state[0]
  216. x_ranges.append(f" ({current_x_min}<= x & x<={sorted_y_group[-1][0]})")
  217. formula += " | ".join(x_ranges)
  218. formula += ") )"
  219. formula += ")"
  220. return formula
  221. def createBalancedDisjunction(indices, name):
  222. #logger.info(f"Creating balanced disjunction for {len(indices)} ({indices}) formulas")
  223. if len(indices) == 0:
  224. return f"formula {name} = false;\n"
  225. else:
  226. while len(indices) > 1:
  227. indices_tmp = [f"({indices[i]} | {indices[i+1]})" for i in range(0,len(indices)//2)]
  228. if len(indices) % 2 == 1:
  229. indices_tmp.append(indices[-1])
  230. indices = indices_tmp
  231. disjunction = f"formula {name} = " + " ".join(indices) + ";\n"
  232. return disjunction
  233. def createUnsafeFormula(clusters):
  234. label = "label \"Unsafe\" = Unsafe;\n"
  235. formulas = ""
  236. indices = list()
  237. for i, cluster in enumerate(clusters):
  238. formulas += f"formula Unsafe_{i} = {clusterFormulaTrimmed(cluster)};\n"
  239. indices.append(f"Unsafe_{i}")
  240. return formulas + "\n" + createBalancedDisjunction(indices, "Unsafe")# + label
  241. def createSafeFormula(clusters):
  242. label = "label \"Safe\" = Safe;\n"
  243. formulas = ""
  244. indices = list()
  245. for i, cluster in enumerate(clusters):
  246. formulas += f"formula Safe_{i} = {clusterFormula(cluster)};\n"
  247. indices.append(f"Safe_{i}")
  248. return formulas + "\n" + createBalancedDisjunction(indices, "Safe")# + label
  249. def updatePrismFile(newFile, iteration, safeStates, unsafeStates):
  250. logger.info("Creating next prism file")
  251. tic()
  252. initFile = f"{newFile}_no_formulas.prism"
  253. newFile = f"{newFile}_{iteration:03}.prism"
  254. exec(f"cp {initFile} {newFile}", verbose=False)
  255. with open(newFile, "a") as prism:
  256. prism.write(createSafeFormula(safeStates))
  257. prism.write(createUnsafeFormula(unsafeStates))
  258. logger.info(f"Creating next prism file - DONE: took {toc()} seconds")
  259. ale = ALEInterface()
  260. #if SDL_SUPPORT:
  261. # ale.setBool("sound", True)
  262. # ale.setBool("display_screen", True)
  263. # Load the ROM file
  264. ale.loadROM(rom_file)
  265. with open('all_positions_v2.pickle', 'rb') as handle:
  266. ramDICT = pickle.load(handle)
  267. y_ram_setting = 60
  268. x = 70
  269. nn_wrapper = SampleFactoryNNQueryWrapper()
  270. experiment_id = int(time.time())
  271. init_mdp = "velocity_safety"
  272. exec(f"mkdir -p images/testing_{experiment_id}", verbose=False)
  273. markerSize = 1
  274. imagesDir = f"images/testing_{experiment_id}"
  275. def drawOntoSkiPosImage(states, color, target_prefix="cluster_", alpha_factor=1.0):
  276. #markerList = {ski_position:list() for ski_position in range(1,num_ski_positions + 1)}
  277. markerList = {(ski_position, velocity):list() for velocity in range(0, num_velocities + 1) for ski_position in range(1,num_ski_positions + 1)}
  278. for state in states:
  279. s = state[0]
  280. #marker = f"-fill 'rgba({color}, {alpha_factor * state[1].ranking})' -draw 'rectangle {s.x-markerSize},{s.y-markerSize} {s.x+markerSize},{s.y+markerSize} '"
  281. marker = f"-fill 'rgba({color}, {alpha_factor * state[1].ranking})' -draw 'point {s.x},{s.y} '"
  282. markerList[(s.ski_position, s.velocity)].append(marker)
  283. for (pos, vel), marker in markerList.items():
  284. command = f"convert {imagesDir}/{target_prefix}_{pos:02}_{vel:02}_individual.png {' '.join(marker)} {imagesDir}/{target_prefix}_{pos:02}_{vel:02}_individual.png"
  285. exec(command, verbose=False)
  286. def concatImages(prefix, iteration):
  287. images = [f"{imagesDir}/{prefix}_{pos:02}_{vel:02}_individual.png" for vel in range(0,num_velocities+1) for pos in range(1,num_ski_positions+1) ]
  288. for vel in range(0, num_velocities + 1):
  289. for pos in range(1, num_ski_positions + 1):
  290. command = f"convert {imagesDir}/{prefix}_{pos:02}_{vel:02}_individual.png "
  291. command += f"-pointsize 10 -gravity NorthEast -annotate +8+0 'p{pos:02}v{vel:02}' "
  292. command += f"{imagesDir}/{prefix}_{pos:02}_{vel:02}_individual.png"
  293. exec(command, verbose=False)
  294. exec(f"montage {' '.join(images)} -geometry +0+0 -tile 8x9 {imagesDir}/{prefix}_{iteration}.png", verbose=False)
  295. #exec(f"sxiv {imagesDir}/{prefix}_{iteration}.png&", verbose=False)
  296. def drawStatesOntoTiledImage(states, color, target, source="images/1_full_scaled_down.png", alpha_factor=1.0):
  297. """
  298. Useful to draw a set of states, e.g. a single cluster
  299. markerList = {1: list(), 2:list(), 3:list(), 4:list(), 5:list(), 6:list(), 7:list(), 8:list()}
  300. logger.info(f"Drawing {len(states)} states onto {target}")
  301. tic()
  302. for state in states:
  303. s = state[0]
  304. marker = f"-fill 'rgba({color}, {alpha_factor * state[1].ranking})' -draw 'rectangle {s.x-markerSize},{s.y-markerSize} {s.x+markerSize},{s.y+markerSize} '"
  305. markerList[s.ski_position].append(marker)
  306. for pos, marker in markerList.items():
  307. command = f"convert {source} {' '.join(marker)} {imagesDir}/{target}_{pos:02}_individual.png"
  308. exec(command, verbose=False)
  309. exec(f"montage {imagesDir}/{target}_*_individual.png -geometry +0+0 -tile x1 {imagesDir}/{target}.png", verbose=False)
  310. logger.info(f"Drawing {len(states)} states onto {target} - Done: took {toc()} seconds")
  311. """
  312. def drawClusters(clusterDict, target, iteration, alpha_factor=1.0):
  313. logger.info(f"Drawing clusters")
  314. tic()
  315. for velocity in range(0, num_velocities + 1):
  316. for ski_position in range(1, num_ski_positions + 1):
  317. source = "images/1_full_scaled_down.png"
  318. exec(f"cp {source} {imagesDir}/{target}_{ski_position:02}_{velocity:02}_individual.png", verbose=False)
  319. for _, clusterStates in clusterDict.items():
  320. color = f"{np.random.choice(range(256))}, {np.random.choice(range(256))}, {np.random.choice(range(256))}"
  321. drawOntoSkiPosImage(clusterStates, color, target, alpha_factor=alpha_factor)
  322. concatImages(target, iteration)
  323. logger.info(f"Drawing clusters - DONE: took {toc()} seconds")
  324. def drawResult(clusterDict, target, iteration):
  325. logger.info(f"Drawing clusters")
  326. for velocity in range(0,num_velocities+1):
  327. for ski_position in range(1, num_ski_positions + 1):
  328. source = "images/1_full_scaled_down.png"
  329. exec(f"cp {source} {imagesDir}/{target}_{ski_position:02}_{velocity:02}_individual.png", verbose=False)
  330. for _, (clusterStates, result) in clusterDict.items():
  331. color = "100,100,100"
  332. if result == Verdict.GOOD:
  333. color = "0,200,0"
  334. elif result == Verdict.BAD:
  335. color = "200,0,0"
  336. drawOntoSkiPosImage(clusterStates, color, target, alpha_factor=0.7)
  337. concatImages(target, iteration)
  338. logger.info(f"Drawing clusters - DONE: took {toc()} seconds")
  339. def _init_logger():
  340. logger = logging.getLogger('main')
  341. logger.setLevel(logging.INFO)
  342. handler = logging.StreamHandler(sys.stdout)
  343. formatter = logging.Formatter( '[%(levelname)s] %(module)s - %(message)s')
  344. handler.setFormatter(formatter)
  345. logger.addHandler(handler)
  346. def clusterImportantStates(ranking, iteration):
  347. logger.info(f"Starting to cluster {len(ranking)} states into clusters")
  348. tic()
  349. states = [[s[0].x,s[0].y, s[0].ski_position * 20, s[0].velocity * 20, s[1].ranking] for s in ranking]
  350. #states = [[s[0].x,s[0].y, s[0].ski_position * 30, s[1].ranking] for s in ranking]
  351. #kmeans = KMeans(n_clusters, random_state=0, n_init="auto").fit(states)
  352. dbscan = DBSCAN(eps=15).fit(states)
  353. labels = dbscan.labels_
  354. n_clusters = len(set(labels)) - (1 if -1 in labels else 0)
  355. logger.info(f"Starting to cluster {len(ranking)} states into clusters - DONE: took {toc()} seconds with {n_clusters} cluster")
  356. clusterDict = {i : list() for i in range(0,n_clusters)}
  357. for i, state in enumerate(ranking):
  358. if labels[i] == -1: continue
  359. clusterDict[labels[i]].append(state)
  360. drawClusters(clusterDict, f"clusters", iteration)
  361. return clusterDict
  362. if __name__ == '__main__':
  363. _init_logger()
  364. logger = logging.getLogger('main')
  365. logger.info("Starting")
  366. n_clusters = 40
  367. testAll = False
  368. safeStates = list()
  369. unsafeStates = list()
  370. iteration = 0
  371. while True:
  372. updatePrismFile(init_mdp, iteration, safeStates, unsafeStates)
  373. computeStateRanking(f"{init_mdp}_{iteration:03}.prism", iteration)
  374. ranking = fillStateRanking(f"action_ranking_{iteration:03}")
  375. sorted_ranking = sorted( (x for x in ranking.items() if x[1].ranking > 0.1), key=lambda x: x[1].ranking)
  376. clusters = clusterImportantStates(sorted_ranking, iteration)
  377. if testAll: failingPerCluster = {i: list() for i in range(0, n_clusters)}
  378. clusterResult = dict()
  379. for id, cluster in clusters.items():
  380. num_tests = int(factor_tests_per_cluster * len(cluster))
  381. num_tests = 1
  382. #logger.info(f"Testing {num_tests} states (from {len(cluster)} states) from cluster {id}")
  383. randomStates = np.random.choice(len(cluster), num_tests, replace=False)
  384. randomStates = [cluster[i] for i in randomStates]
  385. verdictGood = True
  386. for state in randomStates:
  387. x = state[0].x
  388. y = state[0].y
  389. ski_pos = state[0].ski_position
  390. velocity = state[0].velocity
  391. #result = run_single_test(ale,nn_wrapper,x,y,ski_pos, duration=50)
  392. result = run_single_test(ale,nn_wrapper,x,y,ski_pos, velocity, duration=50)
  393. result = Verdict.BAD # TODO REMOVE ME!!!!!!!!!!!!!!
  394. if result == Verdict.BAD:
  395. if testAll:
  396. failingPerCluster[id].append(state)
  397. else:
  398. clusterResult[id] = (cluster, Verdict.BAD)
  399. verdictGood = False
  400. unsafeStates.append(cluster)
  401. break
  402. if verdictGood:
  403. clusterResult[id] = (cluster, Verdict.GOOD)
  404. safeStates.append(cluster)
  405. logger.info(f"Iteration: {iteration:03} -\tSafe Results : {sum([len(c) for c in safeStates])} -\tUnsafe Results:{sum([len(c) for c in unsafeStates])}")
  406. if testAll: drawClusters(failingPerCluster, f"failing", iteration)
  407. #drawResult(clusterResult, "result", iteration)
  408. iteration += 1