utils.py 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964
  1. import json
  2. from sympy import symbols, sympify, log, atan2, pi
  3. from pprint import pprint
  4. import re
  5. import string
  6. import random
  7. import time
  8. from copy import deepcopy
  9. import pickle
  10. import os
  11. def load_json(filename):
  12. with open(filename, "r", encoding="utf-8") as f:
  13. return json.load(f)
  14. def save_json(data, filename):
  15. filename_bk = filename + '.bk'
  16. with open(filename_bk, "w", encoding="utf-8") as f:
  17. json.dump(data, f, ensure_ascii=False, indent=2)
  18. if os.path.exists(filename):
  19. os.remove(filename)
  20. os.rename(filename_bk, filename)
  21. def show_json(dict_data):
  22. pprint(dict_data, sort_dicts=False, compact=True)
  23. print()
  24. def load_pickle(filename):
  25. with open(filename, "rb") as f:
  26. data = pickle.load(f)
  27. return data
  28. def save_pickle(data, filename):
  29. with open(filename, "wb") as f:
  30. pickle.dump(data, f)
  31. def debug_execute(func, debug_execute_args):
  32. timing = time.time()
  33. result = func(*debug_execute_args)
  34. msg = (f"func: {func.__name__}, args: {str(debug_execute_args)}, return: {str(result)}, "
  35. f"take: {round(time.time() - timing, 4)}s.")
  36. if isinstance(result, bool):
  37. if result:
  38. print(f"\033[32m{msg}\033[0m")
  39. else:
  40. print(f"\033[31m{msg}\033[0m")
  41. else:
  42. print(msg)
  43. def parse_fact(s):
  44. """
  45. Parse s to get predicate name and paras.
  46. >> parse_geo_predicate('Predicate(A,B,C)')
  47. ('Predicate', ['A', 'B', 'C'])
  48. """
  49. predicate_name, paras = s.split("(")
  50. paras = paras[:-1].replace(",", "")
  51. return predicate_name, tuple(paras)
  52. def parse_expr(s):
  53. """
  54. Parse str expression to sympy expression.
  55. Args:
  56. s (str): Algebra relation and expression. The components include algebra relation types,
  57. algebraic operations, the symbolic representations of measures and constants. Such as:
  58. 'Eq(Sub(A.y,Add(Mul(l.k,A.x),l.b)))', 'Value(Mul(Sub(C.x,B.x),Sub(A.y,B.y)))'.
  59. Returns:
  60. parsed_s (tuple): Algebra relation type and instance of sympy expression. Such as:
  61. ('Eq', -A.x*l.k + A.y - l.b), ('Value', (A.y - B.y)*(-B.x + C.x)).
  62. """
  63. predicate, expr_str = s.split("(", 1)
  64. expr_str = expr_str[:-1]
  65. if '(' not in expr_str: # such as 'Eq(lk.ma)'
  66. return predicate, symbols(expr_str)
  67. i = 0
  68. j = 0
  69. stack = []
  70. while j < len(expr_str):
  71. if expr_str[j] == "(":
  72. stack.append(expr_str[i:j])
  73. stack.append(expr_str[j])
  74. i = j + 1
  75. elif expr_str[j] == ",":
  76. if i < j:
  77. stack.append(expr_str[i: j])
  78. i = j + 1
  79. else:
  80. i = i + 1
  81. elif expr_str[j] == ")":
  82. if i < j:
  83. stack.append(expr_str[i: j])
  84. i = j + 1
  85. else:
  86. i = i + 1
  87. paras = []
  88. while True:
  89. para = stack.pop()
  90. if para == "(":
  91. break
  92. if type(para) is str:
  93. if '.' in para:
  94. para = symbols(para) # symbol representation of measure
  95. else:
  96. para = sympify(para.replace('{', '(').replace('}', ')')) # constant, free symbols, or expr
  97. paras.append(para)
  98. paras = paras[::-1]
  99. operation = stack.pop()
  100. if operation == 'Add':
  101. result = paras[0]
  102. for p in paras[1:]:
  103. result += p
  104. elif operation == 'Sub':
  105. result = paras[0] - paras[1]
  106. elif operation == 'Mul':
  107. result = paras[0]
  108. for p in paras[1:]:
  109. result *= p
  110. elif operation == 'Div':
  111. result = paras[0] / paras[1]
  112. elif operation == 'Pow':
  113. result = paras[0] ** paras[1]
  114. elif operation == 'Log':
  115. result = log(paras[0])
  116. elif operation == 'Ma':
  117. a_x, a_y, b_x, b_y, c_x, c_y = paras
  118. BA = (a_x - b_x, a_y - b_y) # vector BA
  119. BC = (c_x - b_x, c_y - b_y) # vector BC
  120. angle_BA = atan2(BA[1], BA[0]) # (-π, π]
  121. angle_BC = atan2(BC[1], BC[0]) # (-π, π]
  122. result = (angle_BA - angle_BC) % (2 * pi) # clockwise
  123. else:
  124. e_msg = f"Unknown operation '{operation}' in s '{s}'."
  125. raise Exception(e_msg)
  126. stack.append(result)
  127. j = j + 1
  128. if len(stack) > 1:
  129. e_msg = f"Syntax error in s '{s}': missing ')'?"
  130. raise Exception(e_msg)
  131. return predicate, stack.pop()
  132. def replace_paras(paras, replace):
  133. replaced_paras = [replace[p] for p in paras]
  134. return tuple(replaced_paras)
  135. def replace_expr(expr, replace):
  136. """Replace instances according to the replacement mapping.
  137. Args:
  138. expr (sympy_expr): instance of sympy expression. Such as -A.x*l.k + A.y - l.b.
  139. replace (dict): Keys are the old entity and values are the new entity. Such As {'A': 'B', 'l': 'k'}.
  140. Returns:
  141. replaced_expr: Replaced expr. Such as -B.x*k.k + B.y - k.b.
  142. """
  143. replace_old_to_temp = {}
  144. replace_temp_to_new = {}
  145. for sym_old in expr.free_symbols:
  146. entities_old, attr = str(sym_old).split('.')
  147. sym_temp = symbols("".join([e + "'" for e in entities_old]) + '.' + attr)
  148. replace_old_to_temp[sym_old] = sym_temp
  149. sym_new = symbols("".join([replace[e] for e in entities_old]) + '.' + attr)
  150. replace_temp_to_new[sym_temp] = sym_new
  151. expr = expr.subs(replace_old_to_temp).subs(replace_temp_to_new)
  152. return expr
  153. def parse_disjunctive(s):
  154. if len(s) == 0:
  155. return []
  156. return s.split('&')
  157. def parse_gdl(gdl):
  158. parsed_gdl = {
  159. 'Presets': {},
  160. 'Relations': {},
  161. 'Attributions': {},
  162. 'sym_to_attr': {},
  163. 'Theorems': {},
  164. 'FactAutoExpand': {},
  165. 'GoalAutoExpand': {}
  166. }
  167. for preset in gdl['Presets']:
  168. preset_name, preset_paras = parse_fact(preset)
  169. parsed_gdl['Presets'][preset_name] = {
  170. 'paras': preset_paras
  171. }
  172. for relation in gdl['Relations']:
  173. relation_name, relation_paras = parse_fact(relation)
  174. geometric_constraints = []
  175. for geometric_constraint in parse_disjunctive(gdl['Relations'][relation]['geometric_constraints']):
  176. name, paras = parse_fact(geometric_constraint)
  177. geometric_constraints.append((name, paras))
  178. parsed_gdl['Relations'][relation_name] = {
  179. 'paras': relation_paras,
  180. 'geometric_constraints': tuple(geometric_constraints)
  181. }
  182. for attr in gdl['Attributions']:
  183. attr_name, attr_paras = parse_fact(attr)
  184. geometric_constraints = []
  185. for geometric_constraint in parse_disjunctive(gdl['Attributions'][attr]['geometric_constraints']):
  186. name, paras = parse_fact(geometric_constraint)
  187. geometric_constraints.append((name, paras))
  188. multiple_forms = []
  189. for multi in parse_disjunctive(gdl['Attributions'][attr]['multiple_forms']):
  190. _, multi_paras = parse_fact(multi)
  191. multiple_forms.append(multi_paras)
  192. parsed_gdl['Attributions'][gdl['Attributions'][attr]['sym']] = {
  193. 'name': attr_name,
  194. 'paras': attr_paras,
  195. 'geometric_constraints': tuple(geometric_constraints),
  196. 'multiple_forms': tuple(multiple_forms)
  197. }
  198. for theorem in gdl['Theorems']:
  199. _parse_one_theorem(theorem, gdl, parsed_gdl)
  200. for common_sense in gdl['CommonSense']:
  201. _parse_one_common_sense(common_sense, gdl, parsed_gdl)
  202. return parsed_gdl
  203. def get_theorems():
  204. useful_theorems = set()
  205. for pid in make_train_val_test_split()['test']:
  206. for theorem in load_json(f'../../datasets/problems/{pid}.json')['theorem_seqs']:
  207. useful_theorems.add(theorem.split('(')[0])
  208. # all_theorems = set(parse_gdl(load_json('../../datasets/gdl.json'))['Theorems'])
  209. # print(f'All: {len(all_theorems)}, Useful: {len(useful_theorems)}, Useless: {len(all_theorems - useful_theorems)}')
  210. return useful_theorems
  211. def _parse_one_common_sense(common_sense, gdl, parsed_gdl):
  212. if gdl['CommonSense'][common_sense]['conclusion'].startswith('Eq('):
  213. premise_predicate, premise_paras = parse_fact(gdl['CommonSense'][common_sense]['premises'])
  214. conclusion_predicate, conclusion_expr = parse_expr(gdl['CommonSense'][common_sense]['conclusion'])
  215. if premise_predicate in parsed_gdl['FactAutoExpand']:
  216. replace = dict(zip(premise_paras, parsed_gdl['FactAutoExpand'][premise_predicate]['paras']))
  217. conclusion_expr = replace_expr(conclusion_expr, replace)
  218. parsed_gdl['FactAutoExpand'][premise_predicate]['expand'] = tuple(
  219. list(parsed_gdl['FactAutoExpand'][premise_predicate]['expand']) +
  220. [(conclusion_predicate, conclusion_expr)]
  221. )
  222. else:
  223. parsed_gdl['FactAutoExpand'][premise_predicate] = {
  224. 'paras': premise_paras,
  225. 'expand': ((conclusion_predicate, conclusion_expr),)
  226. }
  227. else:
  228. premise_predicate, premise_paras = parse_fact(gdl['CommonSense'][common_sense]['premises'])
  229. conclusion_predicate, conclusion_paras = parse_fact(gdl['CommonSense'][common_sense]['conclusion'])
  230. if premise_predicate in parsed_gdl['FactAutoExpand']:
  231. replace = dict(zip(premise_paras, parsed_gdl['FactAutoExpand'][premise_predicate]['paras']))
  232. premise_paras = parsed_gdl['FactAutoExpand'][premise_predicate]['paras']
  233. conclusion_paras = replace_paras(conclusion_paras, replace)
  234. parsed_gdl['FactAutoExpand'][premise_predicate]['expand'] = tuple(
  235. list(parsed_gdl['FactAutoExpand'][premise_predicate]['expand']) +
  236. [(conclusion_predicate, conclusion_paras)]
  237. )
  238. else:
  239. parsed_gdl['FactAutoExpand'][premise_predicate] = {
  240. 'paras': premise_paras,
  241. 'expand': ((conclusion_predicate, conclusion_paras),)
  242. }
  243. if len(set(premise_paras) - set(conclusion_paras)) != 0:
  244. return
  245. if conclusion_predicate in parsed_gdl['GoalAutoExpand']:
  246. replace = dict(zip(conclusion_paras, parsed_gdl['GoalAutoExpand'][conclusion_predicate]['paras']))
  247. premise_paras = replace_paras(premise_paras, replace)
  248. parsed_gdl['GoalAutoExpand'][conclusion_predicate]['expand'] = tuple(
  249. list(parsed_gdl['GoalAutoExpand'][conclusion_predicate]['expand']) +
  250. [(premise_predicate, premise_paras)]
  251. )
  252. else:
  253. parsed_gdl['GoalAutoExpand'][conclusion_predicate] = {
  254. 'paras': conclusion_paras,
  255. 'expand': ((premise_predicate, premise_paras),)
  256. }
  257. def _parse_one_theorem(theorem, gdl, parsed_gdl):
  258. theorem_name, theorem_paras = parse_fact(theorem)
  259. geometric_constraints = [] # (predicate, paras)
  260. geometric_premises = [] # (predicate, paras)
  261. algebraic_premises = [] # (expr, paras)
  262. algebraic_constraints = [] # (relation_type, expr, paras)
  263. for premise in parse_disjunctive(gdl['Theorems'][theorem]['premises']):
  264. if premise.startswith('Eq('):
  265. _, expr = parse_expr(premise)
  266. paras = []
  267. for sym in expr.free_symbols:
  268. paras.extend(list(str(sym).split('.')[0]))
  269. algebraic_premises.append((expr, paras))
  270. else:
  271. premise_name, premise_paras = parse_fact(premise)
  272. geometric_premises.append((premise_name, premise_paras))
  273. if premise_name in parsed_gdl['Presets']:
  274. geometric_constraints.append((premise_name, premise_paras))
  275. else:
  276. replace = dict(zip(parsed_gdl['Relations'][premise_name]['paras'], premise_paras))
  277. for predicate, paras in parsed_gdl['Relations'][premise_name]['geometric_constraints']:
  278. paras = replace_paras(paras, replace)
  279. geometric_constraints.append((predicate, paras))
  280. for constraint in parse_disjunctive(gdl['Theorems'][theorem]['algebraic_constraints']):
  281. algebra_relation, expr = parse_expr(constraint)
  282. paras = [str(sym).split('.')[0] for sym in expr.free_symbols]
  283. algebraic_constraints.append((algebra_relation, expr, paras))
  284. entities_gpl = _get_gpl(geometric_constraints, [], algebraic_constraints, theorem_paras)
  285. premises_gpl = _get_gpl(geometric_premises, algebraic_premises, algebraic_constraints, theorem_paras)
  286. # parse theorem conclusions
  287. if gdl['Theorems'][theorem]['conclusion'].startswith('Eq('):
  288. _, expr = parse_expr(gdl['Theorems'][theorem]['conclusion'])
  289. conclusion = ('Eq', expr)
  290. else:
  291. conclusion_name, conclusion_paras = parse_fact(gdl['Theorems'][theorem]['conclusion'])
  292. conclusion = (conclusion_name, conclusion_paras)
  293. # print(gdl['Theorems'][theorem])
  294. parsed_gdl['Theorems'][theorem_name] = {
  295. 'paras': theorem_paras,
  296. 'circle': set(gdl['Theorems'][theorem]['circle']),
  297. 'entities_gpl': entities_gpl,
  298. 'premises_gpl': premises_gpl,
  299. 'conclusion': conclusion
  300. }
  301. def _get_gpl(geometric_premises, algebraic_premises, algebraic_constraints, theorem_paras):
  302. geometric_premises = list(geometric_premises) # (predicate, paras)
  303. algebraic_premises = list(algebraic_premises) # (expr, paras)
  304. algebraic_constraints = list(algebraic_constraints) # (relation_type, expr, paras)
  305. # adjust the execution order
  306. products = []
  307. added_paras = set()
  308. # map para to geometric_premises
  309. paras_to_geometric_premises = {}
  310. for premise_name, premise_paras in geometric_premises:
  311. for p in list(set(premise_paras)):
  312. if p not in paras_to_geometric_premises:
  313. paras_to_geometric_premises[p] = [(premise_name, premise_paras)]
  314. else:
  315. paras_to_geometric_premises[p].append((premise_name, premise_paras))
  316. # add geometric_premise to product, entity p only exist in those geometric_premise
  317. for p in paras_to_geometric_premises:
  318. if len(paras_to_geometric_premises[p]) == 1 and paras_to_geometric_premises[p][0] not in products:
  319. products.append(paras_to_geometric_premises[p][0])
  320. geometric_premises.remove(paras_to_geometric_premises[p][0])
  321. added_paras.update(paras_to_geometric_premises[p][0][1])
  322. # for the remaining geometric_premise, select a portion to add to product, according to:
  323. # 1. the number of not added entities in it paras
  324. # 2. the number of paras
  325. # print(products)
  326. # print(paras_to_geometric_premises)
  327. # print(added_paras)
  328. # print()
  329. while len(added_paras) < len(theorem_paras):
  330. # print(added_paras)
  331. # print(theorem_paras)
  332. # print(theorem_geometric_premises)
  333. max_index = 0
  334. max_not_added_paras_len = len(set(geometric_premises[0][1]) - added_paras)
  335. max_paras_len = len(geometric_premises[0][1])
  336. for i in range(1, len(geometric_premises)):
  337. not_added_paras_len = len(set(geometric_premises[i][1]) - added_paras)
  338. paras_len = len(geometric_premises[i][1])
  339. if not_added_paras_len > max_not_added_paras_len or (
  340. not_added_paras_len == max_not_added_paras_len and paras_len > max_paras_len):
  341. max_index = i
  342. max_not_added_paras_len = not_added_paras_len
  343. max_paras_len = paras_len
  344. products.append(geometric_premises[max_index])
  345. added_paras.update(geometric_premises[max_index][1])
  346. geometric_premises.pop(max_index)
  347. # sort product according to the number of its paras
  348. products.sort(key=len, reverse=True)
  349. gpl = []
  350. added_paras = []
  351. for predicate, paras in products:
  352. inherent_same_index = []
  353. for i in range(len(paras)):
  354. for j in range(i + 1, len(paras)):
  355. if paras[i] == paras[j]:
  356. inherent_same_index.append((i, j))
  357. mutual_same_index = []
  358. for i in range(len(added_paras)):
  359. for j in range(len(paras)):
  360. if added_paras[i] == paras[j]:
  361. mutual_same_index.append((i, j))
  362. added_index = []
  363. for j in range(len(paras)):
  364. if paras[j] not in added_paras:
  365. added_index.append(j)
  366. added_paras.append(paras[j])
  367. geometric_premise = _get_geometric_premise(geometric_premises, added_paras) # (predicate, paras)
  368. algebraic_premise = _get_algebraic_premise(algebraic_premises, added_paras) # (expr)
  369. algebraic_constraint = _get_algebraic_constraint(algebraic_constraints, added_paras) # (relation_type, expr)
  370. gpl.append({
  371. "product": (predicate, paras, tuple(inherent_same_index), tuple(mutual_same_index), tuple(added_index)),
  372. "geometric_premises": geometric_premise,
  373. "algebraic_premises": algebraic_premise,
  374. "algebraic_constraints": algebraic_constraint
  375. })
  376. if len(geometric_premises) > 0 or len(algebraic_premises) > 0 or len(algebraic_constraints) > 0:
  377. e_msg = f"There exist unadded constraints."
  378. raise Exception(e_msg)
  379. return tuple(gpl)
  380. def _get_algebraic_constraint(algebraic_constraints, added_paras):
  381. algebraic_constraint = [] # (relation_type, expr, paras)
  382. for i in range(len(algebraic_constraints))[::-1]:
  383. ac_check_type, ac_check_expr, ac_check_paras = algebraic_constraints[i]
  384. if len(set(ac_check_paras) - set(added_paras)) == 0:
  385. algebraic_constraint.append(algebraic_constraints[i])
  386. algebraic_constraints.pop(i)
  387. # sort according to the number of paras
  388. algebraic_constraint = sorted(algebraic_constraint, key=lambda x: (len(x[2]), len(set(x[2]))), reverse=True)
  389. algebraic_constraint = tuple([(relation_type, expr) for relation_type, expr, _ in algebraic_constraint])
  390. return algebraic_constraint
  391. def _get_geometric_premise(geometric_premises, added_paras):
  392. geometric_premise = [] # (predicate, paras)
  393. for i in range(len(geometric_premises))[::-1]:
  394. geometric_premises_predicate, geometric_premises_paras = geometric_premises[i]
  395. if len(set(geometric_premises_paras) - set(added_paras)) == 0:
  396. geometric_premise.append(geometric_premises[i])
  397. geometric_premises.pop(i)
  398. # sort according to the number of paras
  399. geometric_premise = tuple(sorted(geometric_premise, key=lambda x: (len(x[1]), len(set(x[1]))), reverse=True))
  400. return geometric_premise
  401. def _get_algebraic_premise(algebraic_premises, added_paras):
  402. algebraic_premise = [] # (expr, paras)
  403. for i in range(len(algebraic_premises))[::-1]:
  404. algebraic_premises_expr, algebraic_premises_paras = algebraic_premises[i]
  405. if len(set(algebraic_premises_paras) - set(added_paras)) == 0:
  406. algebraic_premise.append(algebraic_premises[i])
  407. algebraic_premises.pop(i)
  408. algebraic_premise = sorted(algebraic_premise, key=lambda x: (len(x[1]), len(set(x[1]))), reverse=True)
  409. algebraic_premise = tuple([expr for expr, _ in algebraic_premise])
  410. return algebraic_premise
  411. def parse_cdl(cdl):
  412. construction_cdl = []
  413. for one_cdl in cdl['construction_cdl']:
  414. if one_cdl.startswith("Shape"):
  415. predicate, paras = one_cdl.split('(')
  416. paras = tuple(paras[:-1].split(','))
  417. elif one_cdl.startswith('Collinear'):
  418. predicate, paras = one_cdl.split('(')
  419. paras = tuple(paras[:-1])
  420. else:
  421. predicate, paras = one_cdl.split('(')
  422. paras = tuple(paras[:-1].replace(',', ''))
  423. construction_cdl.append((predicate, paras))
  424. points = {}
  425. for point in cdl['points']:
  426. points[point] = tuple(cdl['points'][point])
  427. relation_cdl = []
  428. for one_cdl in cdl['text_cdl'] + cdl['image_cdl']:
  429. if one_cdl.startswith('Eq('):
  430. fact = parse_expr(one_cdl)
  431. else:
  432. fact = parse_fact(one_cdl)
  433. if fact not in relation_cdl:
  434. relation_cdl.append(fact)
  435. if cdl['goal_cdl'].startswith('Eq('):
  436. goal_cdl = parse_expr(cdl['goal_cdl'])
  437. else:
  438. goal_cdl = parse_fact(cdl['goal_cdl'])
  439. parsed_cdl = {
  440. 'problem_id': cdl['problem_id'],
  441. 'construction_cdl': tuple(construction_cdl),
  442. 'points': points,
  443. 'relation_cdl': tuple(relation_cdl),
  444. 'goal_cdl': goal_cdl
  445. }
  446. # for predicate, instance in parsed_cdl['relation_cdl']:
  447. # if predicate == 'Eq':
  448. # for sym in instance.free_symbols:
  449. # print(f'{str(sym)}: ', sym == symbols(str(sym)))
  450. return parsed_cdl
  451. def get_used_theorems():
  452. used_theorems = set()
  453. for pid in range(7000):
  454. pid += 1
  455. for theorem in load_json(f'../../datasets/problems/{pid}.json')['theorem_seqs']:
  456. used_theorems.add(theorem.split('(')[0])
  457. return sorted(list(used_theorems))
  458. expr_letters = tuple( # letters in algebraic expr
  459. ['+', '-', '**', '*', '/', 'sqrt', 'number', 'pi', '(', ')'] +
  460. sorted(['.' + attr_sym for attr_sym in parse_gdl(load_json('../../datasets/gdl.json'))['Attributions'].keys()])
  461. )
  462. theorem_letters = tuple( # theorem letters (theorem vocab)
  463. ['solve_eq'] + get_used_theorems()
  464. # sorted(list(parse_gdl(load_json('../../datasets/gdl.json'))['Theorems'].keys()))
  465. )
  466. state_letters = tuple( # letters in serialized problem state
  467. ['padding'] +
  468. list(expr_letters) + # letters in algebraic expr
  469. [ # delimiter letter
  470. ',', '&', '|', # split facts
  471. '<construction>', # construction
  472. '<init_fact>', '<premise>', '<apply_theorem>', '<conclusion>', # forward
  473. '<init_goal>', '<goal>', '<decompose>', '<sub_goals>' # backward
  474. ] +
  475. sorted([r for r in parse_gdl(load_json('../../datasets/gdl.json'))['Presets'].keys()]) + # Predicate
  476. sorted([r for r in parse_gdl(load_json('../../datasets/gdl.json'))['Relations'].keys()]) + # Predicate
  477. list(string.ascii_letters) + # parameters
  478. list(theorem_letters) # # theorem letters (theorem vocab)
  479. )
  480. def _anti_parse_operation(operation):
  481. operation_type, operation_predicate, operation_instance = operation
  482. if operation_type == 'Preset':
  483. return 'Preset: ' + operation_predicate
  484. elif operation_type == 'Apply':
  485. return 'Apply: ' + operation_predicate + '(' + ','.join(operation_instance) + ')'
  486. elif operation_type == 'Decompose':
  487. return 'Decompose: ' + operation_predicate + '(' + ','.join(operation_instance) + ')'
  488. else:
  489. raise Exception(f"Unknown operation type '{operation_type}'.")
  490. def _serialize_fact(predicate, instance):
  491. if predicate == 'Eq':
  492. # print(instance)
  493. serialized_expr = ['Eq']
  494. expr = str(instance).replace(' ', '') # remove ' '
  495. for matched in re.findall(r'\d+\.*\d*', expr): # replace number with 'nums'
  496. expr = expr.replace(matched, 'number', 1)
  497. i = 0
  498. while i < len(expr): # serialize
  499. added = False
  500. for matched_part in expr_letters: # expr letters
  501. if expr[i:].startswith(matched_part):
  502. serialized_expr.append(matched_part)
  503. i = i + len(matched_part)
  504. added = True
  505. break
  506. if not added: # entity letters
  507. serialized_expr.append(expr[i])
  508. i = i + 1
  509. # print(serialized_expr)
  510. # print()
  511. return serialized_expr
  512. else:
  513. return [predicate] + list(instance)
  514. def _serialize_operation(operation):
  515. operation_type, operation_predicate, operation_instance = operation
  516. if operation_type == 'Preset':
  517. return [operation_predicate]
  518. elif operation_type == 'Apply':
  519. return [operation_predicate] + list(operation_instance)
  520. elif operation_type == 'Decompose':
  521. return [operation_predicate] + list(operation_instance)
  522. else:
  523. raise Exception(f"Unknown operation type '{operation_type}'.")
  524. def _anti_parse_fact(fact):
  525. predicate, instance = fact
  526. if predicate == 'Eq':
  527. return f"Eq({str(instance).replace(' ', '')})"
  528. else:
  529. return f"{predicate}({','.join(instance)})"
  530. precision = 15
  531. chop = 1e-10
  532. def _satisfy_eq(expr, sym_to_value=None):
  533. try:
  534. if sym_to_value is None:
  535. return expr.evalf(n=precision, chop=chop) == 0
  536. return expr.subs(sym_to_value).evalf(n=precision, chop=chop) == 0
  537. except Exception:
  538. return False
  539. def _satisfy_g(expr, sym_to_value=None):
  540. try:
  541. if sym_to_value is None:
  542. return expr.evalf(n=precision, chop=chop) > 0
  543. return expr.subs(sym_to_value).evalf(n=precision, chop=chop) > 0
  544. except Exception:
  545. return False
  546. def _satisfy_geq(expr, sym_to_value=None):
  547. try:
  548. if sym_to_value is None:
  549. return expr.evalf(n=precision, chop=chop) >= 0
  550. return expr.subs(sym_to_value).evalf(n=precision, chop=chop) >= 0
  551. except Exception:
  552. return False
  553. def _satisfy_l(expr, sym_to_value=None):
  554. try:
  555. if sym_to_value is None:
  556. return expr.evalf(n=precision, chop=chop) < 0
  557. return expr.subs(sym_to_value).evalf(n=precision, chop=chop) < 0
  558. except Exception:
  559. return False
  560. def _satisfy_leq(expr, sym_to_value=None):
  561. try:
  562. if sym_to_value is None:
  563. return expr.evalf(n=precision, chop=chop) <= 0
  564. # print('Leq')
  565. # print(expr)
  566. # print(sym_to_value)
  567. # print(expr.subs(sym_to_value))
  568. # print(expr.subs(sym_to_value).evalf(n=precision, chop=chop))
  569. # print((expr / pi * 180).subs(sym_to_value).evalf(n=precision, chop=chop))
  570. # print(expr.subs(sym_to_value).evalf(n=precision, chop=chop) <= 0)
  571. # print()
  572. return expr.subs(sym_to_value).evalf(n=precision, chop=chop) <= 0
  573. except Exception:
  574. return False
  575. def _satisfy_ueq(expr, sym_to_value=None):
  576. try:
  577. if sym_to_value is None:
  578. return expr.evalf(n=precision, chop=chop) != 0
  579. return expr.subs(sym_to_value).evalf(n=precision, chop=chop) != 0
  580. except Exception:
  581. return False
  582. _satisfy_algebraic = {'Eq': _satisfy_eq, 'G': _satisfy_g, 'Geq': _satisfy_geq,
  583. 'L': _satisfy_l, 'Leq': _satisfy_leq, 'Ueq': _satisfy_ueq}
  584. def get_theorem_seqs(problem):
  585. theorem_seqs = []
  586. goal_related_premise_ids = list(problem.premise_ids_of_goal[0])
  587. goal_related_operation_ids = set()
  588. for fact_id in goal_related_premise_ids:
  589. goal_related_operation_ids.add(problem.facts[fact_id][3])
  590. for new_fact_id in problem.facts[fact_id][2]:
  591. if new_fact_id not in goal_related_premise_ids:
  592. goal_related_premise_ids.append(new_fact_id)
  593. for operation_id in range(len(problem.operations)):
  594. if operation_id not in goal_related_operation_ids:
  595. continue
  596. operation_type, operation_predicate, operation_instance = problem.operations[operation_id]
  597. if operation_type != 'Apply':
  598. continue
  599. theorem_seqs.append(operation_predicate + '(' + ','.join(operation_instance) + ')')
  600. return theorem_seqs
  601. def get_cleaned_theorem_seqs(problem_initial, theorem_seqs):
  602. theorem_seqs = deepcopy(theorem_seqs)
  603. for i in range(len(theorem_seqs))[::-1]: # try delete theorem i
  604. problem = deepcopy(problem_initial)
  605. for j in range(len(theorem_seqs)): # not apply theorem i
  606. if j == i:
  607. continue
  608. problem.apply(theorem_seqs[j])
  609. if problem.status_of_goal[0] == 1: # theorem i can delete
  610. theorem_seqs.pop(i)
  611. return theorem_seqs
  612. def get_dag(applied_theorems, edges):
  613. n = len(applied_theorems) # 这里去重的代码,还能再优化下
  614. closure = [[False] * n for _ in range(n)]
  615. for head, tail in edges:
  616. closure[applied_theorems.index(head)][applied_theorems.index(tail)] = True
  617. for k in range(n):
  618. for i in range(n):
  619. for j in range(n):
  620. if closure[i][k] and closure[k][j]:
  621. closure[i][j] = True
  622. for i in range(n):
  623. for j in range(n):
  624. if closure[i][j]:
  625. for k in range(n):
  626. if k != i and k != j and closure[i][k] and closure[k][j]:
  627. if (applied_theorems[i], applied_theorems[j]) in edges:
  628. edges.remove((applied_theorems[i], applied_theorems[j]))
  629. break
  630. dag = {
  631. 'in_degree': {},
  632. 'out_degree': {},
  633. 'edges': []
  634. }
  635. for theorem in applied_theorems:
  636. dag['in_degree'][theorem] = 0
  637. dag['out_degree'][theorem] = 0
  638. for head, tail in edges:
  639. dag['in_degree'][tail] += 1
  640. dag['out_degree'][head] += 1
  641. dag['edges'] = edges
  642. return dag
  643. def get_forward_dag(problem_initial, theorem_seqs):
  644. theorem_seqs = deepcopy(theorem_seqs)
  645. previous_problem = deepcopy(problem_initial)
  646. applied_theorems = []
  647. edges = []
  648. while len(theorem_seqs) > 0:
  649. for i in range(len(theorem_seqs))[::-1]:
  650. problem = deepcopy(previous_problem)
  651. if not problem.apply(theorem_seqs[i]): # check whether theorem i can apply under previous theorems
  652. continue
  653. dependent_theorems = deepcopy(applied_theorems)
  654. for j in range(len(dependent_theorems))[::-1]: # check whether theorem j is dependent
  655. problem = deepcopy(problem_initial)
  656. for k in range(len(dependent_theorems)): # not apply theorem k=j
  657. if k == j:
  658. continue
  659. problem.apply(dependent_theorems[k])
  660. if problem.apply(theorem_seqs[i]): # still can apply theorem i after delete theorem j
  661. dependent_theorems.pop(j)
  662. check_theorem = theorem_seqs.pop(i)
  663. applied_theorems.append(check_theorem)
  664. previous_problem.apply(check_theorem)
  665. for dependent_theorem in dependent_theorems:
  666. edges.append((dependent_theorem, check_theorem))
  667. return get_dag(applied_theorems, edges)
  668. def get_backward_dag(problem_initial, theorem_seqs):
  669. theorem_seqs = deepcopy(theorem_seqs)
  670. previous_problem = deepcopy(problem_initial)
  671. applied_theorems = []
  672. edges = []
  673. while len(theorem_seqs) > 0:
  674. for i in range(len(theorem_seqs))[::-1]:
  675. problem = deepcopy(previous_problem)
  676. if not problem.decompose(theorem_seqs[i]): # check whether theorem i can apply under previous theorems
  677. continue
  678. dependent_theorems = deepcopy(applied_theorems)
  679. for j in range(len(dependent_theorems))[::-1]: # check whether theorem j is dependent
  680. problem = deepcopy(problem_initial)
  681. for k in range(len(dependent_theorems)): # not apply theorem k=j
  682. if k == j:
  683. continue
  684. problem.decompose(dependent_theorems[k])
  685. if problem.decompose(theorem_seqs[i]): # still can apply theorem i after delete theorem j
  686. dependent_theorems.pop(j)
  687. check_theorem = theorem_seqs.pop(i)
  688. applied_theorems.append(check_theorem)
  689. previous_problem.decompose(check_theorem)
  690. for dependent_theorem in dependent_theorems:
  691. edges.append((dependent_theorem, check_theorem))
  692. return get_dag(applied_theorems, edges)
  693. def inverse_parse_theorem(theorem):
  694. operation_type, operation_predicate, operation_instance = theorem
  695. if operation_type == 'Preset':
  696. return operation_predicate
  697. else:
  698. return operation_predicate + '(' + ','.join(operation_instance) + ')'
  699. def inverse_parse_cdl(predicate, instance):
  700. if predicate in _satisfy_algebraic.keys():
  701. return predicate + '(' + str(instance).replace(' ', '') + ')'
  702. else:
  703. return predicate + '(' + ','.join(instance) + ')'
  704. def get_meta_hypertree(problem):
  705. """
  706. Generate meta hypertree message for downstream task.
  707. :return nodes: all nodes, {node_id: node_name}, such as {1: 'Equation(ll_ab-1)'}
  708. :return edges: all edges, {edge_id: edge_name}, such as {1: "extended"}
  709. :return free_nodes: nodes not in hypertree but in prerequisite, [node_id], such as [1, 2, 3]
  710. :return target_node_id: target node id, such as 1
  711. :return hypertree: {((tail_node_ids), edge_id): (tail_node_ids))}, such as {((1, 2, 3), 1): (4, 5))}
  712. """
  713. group = {} # (premise, theorem): [_id], used for building hyper graph.
  714. cdl = {} # _id: anti_parsed_cdl, user for getting cdl by id.
  715. init_nodes = [] # [_id], id of prerequisite.
  716. tree_nodes = [] # [_id], id of tree nodes.
  717. target_node_id = None
  718. for fact_id in range(len(problem.facts)):
  719. predicate, instance, premise_ids, operation_id = problem.facts[fact_id]
  720. premise_ids = tuple(sorted(list(premise_ids)))
  721. theorem = inverse_parse_theorem(problem.operations[operation_id])
  722. if theorem == "extend_construction": # 不需要这些节点
  723. continue
  724. cdl[fact_id] = inverse_parse_cdl(predicate, instance)
  725. if theorem in {'init_construction', 'init_fact'}: # root nodes
  726. init_nodes.append(fact_id)
  727. continue
  728. if (premise_ids, theorem) not in group:
  729. group[(premise_ids, theorem)] = [fact_id]
  730. else:
  731. group[(premise_ids, theorem)].append(fact_id)
  732. if len(problem.goals) > 0 and problem.status_of_goal[0] == 1:
  733. predicate, instance, _, _ = problem.goals[0]
  734. if predicate == 'Eq' and (predicate, instance) not in problem.fact_id:
  735. target_node_id = len(problem.facts)
  736. cdl[target_node_id] = predicate + '(' + str(instance).replace(' ', '') + ')'
  737. premise_ids = tuple(sorted(list(problem.premise_ids_of_goal[0])))
  738. group[(premise_ids, 'solve_eq')] = [target_node_id]
  739. else:
  740. target_node_id = problem.fact_id[(predicate, instance)]
  741. # for cdl_key in cdl.keys():
  742. # print(cdl_key, cdl[cdl_key])
  743. # print()
  744. #
  745. # for group_key in group:
  746. # print(group_key, group[group_key])
  747. # print()
  748. edges = {-2: "none", -1: "self"}
  749. tree = {}
  750. for premise, theorem in group:
  751. conclusion = group[(premise, theorem)]
  752. edge_id = len(edges)
  753. edges[edge_id] = theorem
  754. adjust_premise = []
  755. for fact_id in premise:
  756. if fact_id in cdl:
  757. adjust_premise.append(fact_id)
  758. else:
  759. _, _, premise_ids, _ = problem.facts[fact_id]
  760. adjust_premise.extend(premise_ids)
  761. adjust_premise = sorted(list(set(adjust_premise)))
  762. tree_nodes += adjust_premise
  763. tree_nodes += conclusion
  764. tree[(tuple(adjust_premise), edge_id)] = conclusion
  765. nodes = {}
  766. for node_id in sorted(list(set(tree_nodes + init_nodes))):
  767. nodes[node_id] = cdl[node_id]
  768. free_nodes = sorted(list(set(init_nodes) - set(tree_nodes)))
  769. return nodes, edges, free_nodes, target_node_id, tree
  770. def make_train_val_test_split(random_seed=0, data_split=(4, 1, 1)):
  771. filename = "../../outputs/log/log_data_problem_split.json"
  772. if os.path.exists(filename):
  773. return load_json(filename)
  774. problem_ids = list(range(1, 7001))
  775. random.Random(random_seed).shuffle(problem_ids)
  776. train, val, test = data_split
  777. train_problem_ids = sorted(problem_ids[:int(7000 * train / (train + val + test))])
  778. val_problem_ids = sorted(problem_ids[int(7000 * train / (train + val + test)):
  779. int(7000 * (train + val) / (train + val + test))])
  780. test_problem_ids = sorted(problem_ids[int(7000 * (train + val) / (train + val + test)):])
  781. problem_split = {"train": train_problem_ids, "val": val_problem_ids, "test": test_problem_ids}
  782. print(f"train: {len(train_problem_ids)}, val: {len(val_problem_ids)}, test: {len(test_problem_ids)}")
  783. save_json(problem_split, filename)
  784. return problem_split