test_facts.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. from sympy.core.facts import (deduce_alpha_implications,
  2. apply_beta_to_alpha_route, rules_2prereq, FactRules, FactKB)
  3. from sympy.core.logic import And, Not
  4. from sympy.testing.pytest import raises
  5. T = True
  6. F = False
  7. U = None
  8. def test_deduce_alpha_implications():
  9. def D(i):
  10. I = deduce_alpha_implications(i)
  11. P = rules_2prereq({
  12. (k, True): {(v, True) for v in S} for k, S in I.items()})
  13. return I, P
  14. # transitivity
  15. I, P = D([('a', 'b'), ('b', 'c')])
  16. assert I == {'a': {'b', 'c'}, 'b': {'c'}, Not('b'):
  17. {Not('a')}, Not('c'): {Not('a'), Not('b')}}
  18. assert P == {'a': {'b', 'c'}, 'b': {'a', 'c'}, 'c': {'a', 'b'}}
  19. # Duplicate entry
  20. I, P = D([('a', 'b'), ('b', 'c'), ('b', 'c')])
  21. assert I == {'a': {'b', 'c'}, 'b': {'c'}, Not('b'): {Not('a')}, Not('c'): {Not('a'), Not('b')}}
  22. assert P == {'a': {'b', 'c'}, 'b': {'a', 'c'}, 'c': {'a', 'b'}}
  23. # see if it is tolerant to cycles
  24. assert D([('a', 'a'), ('a', 'a')]) == ({}, {})
  25. assert D([('a', 'b'), ('b', 'a')]) == (
  26. {'a': {'b'}, 'b': {'a'}, Not('a'): {Not('b')}, Not('b'): {Not('a')}},
  27. {'a': {'b'}, 'b': {'a'}})
  28. # see if it catches inconsistency
  29. raises(ValueError, lambda: D([('a', Not('a'))]))
  30. raises(ValueError, lambda: D([('a', 'b'), ('b', Not('a'))]))
  31. raises(ValueError, lambda: D([('a', 'b'), ('b', 'c'), ('b', 'na'),
  32. ('na', Not('a'))]))
  33. # see if it handles implications with negations
  34. I, P = D([('a', Not('b')), ('c', 'b')])
  35. assert I == {'a': {Not('b'), Not('c')}, 'b': {Not('a')}, 'c': {'b', Not('a')}, Not('b'): {Not('c')}}
  36. assert P == {'a': {'b', 'c'}, 'b': {'a', 'c'}, 'c': {'a', 'b'}}
  37. I, P = D([(Not('a'), 'b'), ('a', 'c')])
  38. assert I == {'a': {'c'}, Not('a'): {'b'}, Not('b'): {'a',
  39. 'c'}, Not('c'): {Not('a'), 'b'},}
  40. assert P == {'a': {'b', 'c'}, 'b': {'a', 'c'}, 'c': {'a', 'b'}}
  41. # Long deductions
  42. I, P = D([('a', 'b'), ('b', 'c'), ('c', 'd'), ('d', 'e')])
  43. assert I == {'a': {'b', 'c', 'd', 'e'}, 'b': {'c', 'd', 'e'},
  44. 'c': {'d', 'e'}, 'd': {'e'}, Not('b'): {Not('a')},
  45. Not('c'): {Not('a'), Not('b')}, Not('d'): {Not('a'), Not('b'),
  46. Not('c')}, Not('e'): {Not('a'), Not('b'), Not('c'), Not('d')}}
  47. assert P == {'a': {'b', 'c', 'd', 'e'}, 'b': {'a', 'c', 'd',
  48. 'e'}, 'c': {'a', 'b', 'd', 'e'}, 'd': {'a', 'b', 'c', 'e'},
  49. 'e': {'a', 'b', 'c', 'd'}}
  50. # something related to real-world
  51. I, P = D([('rat', 'real'), ('int', 'rat')])
  52. assert I == {'int': {'rat', 'real'}, 'rat': {'real'},
  53. Not('real'): {Not('rat'), Not('int')}, Not('rat'): {Not('int')}}
  54. assert P == {'rat': {'int', 'real'}, 'real': {'int', 'rat'},
  55. 'int': {'rat', 'real'}}
  56. # TODO move me to appropriate place
  57. def test_apply_beta_to_alpha_route():
  58. APPLY = apply_beta_to_alpha_route
  59. # indicates empty alpha-chain with attached beta-rule #bidx
  60. def Q(bidx):
  61. return (set(), [bidx])
  62. # x -> a &(a,b) -> x -- x -> a
  63. A = {'x': {'a'}}
  64. B = [(And('a', 'b'), 'x')]
  65. assert APPLY(A, B) == {'x': ({'a'}, []), 'a': Q(0), 'b': Q(0)}
  66. # x -> a &(a,!x) -> b -- x -> a
  67. A = {'x': {'a'}}
  68. B = [(And('a', Not('x')), 'b')]
  69. assert APPLY(A, B) == {'x': ({'a'}, []), Not('x'): Q(0), 'a': Q(0)}
  70. # x -> a b &(a,b) -> c -- x -> a b c
  71. A = {'x': {'a', 'b'}}
  72. B = [(And('a', 'b'), 'c')]
  73. assert APPLY(A, B) == \
  74. {'x': ({'a', 'b', 'c'}, []), 'a': Q(0), 'b': Q(0)}
  75. # x -> a &(a,b) -> y -- x -> a [#0]
  76. A = {'x': {'a'}}
  77. B = [(And('a', 'b'), 'y')]
  78. assert APPLY(A, B) == {'x': ({'a'}, [0]), 'a': Q(0), 'b': Q(0)}
  79. # x -> a b c &(a,b) -> c -- x -> a b c
  80. A = {'x': {'a', 'b', 'c'}}
  81. B = [(And('a', 'b'), 'c')]
  82. assert APPLY(A, B) == \
  83. {'x': ({'a', 'b', 'c'}, []), 'a': Q(0), 'b': Q(0)}
  84. # x -> a b &(a,b,c) -> y -- x -> a b [#0]
  85. A = {'x': {'a', 'b'}}
  86. B = [(And('a', 'b', 'c'), 'y')]
  87. assert APPLY(A, B) == \
  88. {'x': ({'a', 'b'}, [0]), 'a': Q(0), 'b': Q(0), 'c': Q(0)}
  89. # x -> a b &(a,b) -> c -- x -> a b c d
  90. # c -> d c -> d
  91. A = {'x': {'a', 'b'}, 'c': {'d'}}
  92. B = [(And('a', 'b'), 'c')]
  93. assert APPLY(A, B) == {'x': ({'a', 'b', 'c', 'd'}, []),
  94. 'c': ({'d'}, []), 'a': Q(0), 'b': Q(0)}
  95. # x -> a b &(a,b) -> c -- x -> a b c d e
  96. # c -> d &(c,d) -> e c -> d e
  97. A = {'x': {'a', 'b'}, 'c': {'d'}}
  98. B = [(And('a', 'b'), 'c'), (And('c', 'd'), 'e')]
  99. assert APPLY(A, B) == {'x': ({'a', 'b', 'c', 'd', 'e'}, []),
  100. 'c': ({'d', 'e'}, []), 'a': Q(0), 'b': Q(0), 'd': Q(1)}
  101. # x -> a b &(a,y) -> z -- x -> a b y z
  102. # &(a,b) -> y
  103. A = {'x': {'a', 'b'}}
  104. B = [(And('a', 'y'), 'z'), (And('a', 'b'), 'y')]
  105. assert APPLY(A, B) == {'x': ({'a', 'b', 'y', 'z'}, []),
  106. 'a': (set(), [0, 1]), 'y': Q(0), 'b': Q(1)}
  107. # x -> a b &(a,!b) -> c -- x -> a b
  108. A = {'x': {'a', 'b'}}
  109. B = [(And('a', Not('b')), 'c')]
  110. assert APPLY(A, B) == \
  111. {'x': ({'a', 'b'}, []), 'a': Q(0), Not('b'): Q(0)}
  112. # !x -> !a !b &(!a,b) -> c -- !x -> !a !b
  113. A = {Not('x'): {Not('a'), Not('b')}}
  114. B = [(And(Not('a'), 'b'), 'c')]
  115. assert APPLY(A, B) == \
  116. {Not('x'): ({Not('a'), Not('b')}, []), Not('a'): Q(0), 'b': Q(0)}
  117. # x -> a b &(b,c) -> !a -- x -> a b
  118. A = {'x': {'a', 'b'}}
  119. B = [(And('b', 'c'), Not('a'))]
  120. assert APPLY(A, B) == {'x': ({'a', 'b'}, []), 'b': Q(0), 'c': Q(0)}
  121. # x -> a b &(a, b) -> c -- x -> a b c p
  122. # c -> p a
  123. A = {'x': {'a', 'b'}, 'c': {'p', 'a'}}
  124. B = [(And('a', 'b'), 'c')]
  125. assert APPLY(A, B) == {'x': ({'a', 'b', 'c', 'p'}, []),
  126. 'c': ({'p', 'a'}, []), 'a': Q(0), 'b': Q(0)}
  127. def test_FactRules_parse():
  128. f = FactRules('a -> b')
  129. assert f.prereq == {'b': {'a'}, 'a': {'b'}}
  130. f = FactRules('a -> !b')
  131. assert f.prereq == {'b': {'a'}, 'a': {'b'}}
  132. f = FactRules('!a -> b')
  133. assert f.prereq == {'b': {'a'}, 'a': {'b'}}
  134. f = FactRules('!a -> !b')
  135. assert f.prereq == {'b': {'a'}, 'a': {'b'}}
  136. f = FactRules('!z == nz')
  137. assert f.prereq == {'z': {'nz'}, 'nz': {'z'}}
  138. def test_FactRules_parse2():
  139. raises(ValueError, lambda: FactRules('a -> !a'))
  140. def test_FactRules_deduce():
  141. f = FactRules(['a -> b', 'b -> c', 'b -> d', 'c -> e'])
  142. def D(facts):
  143. kb = FactKB(f)
  144. kb.deduce_all_facts(facts)
  145. return kb
  146. assert D({'a': T}) == {'a': T, 'b': T, 'c': T, 'd': T, 'e': T}
  147. assert D({'b': T}) == { 'b': T, 'c': T, 'd': T, 'e': T}
  148. assert D({'c': T}) == { 'c': T, 'e': T}
  149. assert D({'d': T}) == { 'd': T }
  150. assert D({'e': T}) == { 'e': T}
  151. assert D({'a': F}) == {'a': F }
  152. assert D({'b': F}) == {'a': F, 'b': F }
  153. assert D({'c': F}) == {'a': F, 'b': F, 'c': F }
  154. assert D({'d': F}) == {'a': F, 'b': F, 'd': F }
  155. assert D({'a': U}) == {'a': U} # XXX ok?
  156. def test_FactRules_deduce2():
  157. # pos/neg/zero, but the rules are not sufficient to derive all relations
  158. f = FactRules(['pos -> !neg', 'pos -> !z'])
  159. def D(facts):
  160. kb = FactKB(f)
  161. kb.deduce_all_facts(facts)
  162. return kb
  163. assert D({'pos': T}) == {'pos': T, 'neg': F, 'z': F}
  164. assert D({'pos': F}) == {'pos': F }
  165. assert D({'neg': T}) == {'pos': F, 'neg': T }
  166. assert D({'neg': F}) == { 'neg': F }
  167. assert D({'z': T}) == {'pos': F, 'z': T}
  168. assert D({'z': F}) == { 'z': F}
  169. # pos/neg/zero. rules are sufficient to derive all relations
  170. f = FactRules(['pos -> !neg', 'neg -> !pos', 'pos -> !z', 'neg -> !z'])
  171. assert D({'pos': T}) == {'pos': T, 'neg': F, 'z': F}
  172. assert D({'pos': F}) == {'pos': F }
  173. assert D({'neg': T}) == {'pos': F, 'neg': T, 'z': F}
  174. assert D({'neg': F}) == { 'neg': F }
  175. assert D({'z': T}) == {'pos': F, 'neg': F, 'z': T}
  176. assert D({'z': F}) == { 'z': F}
  177. def test_FactRules_deduce_multiple():
  178. # deduction that involves _several_ starting points
  179. f = FactRules(['real == pos | npos'])
  180. def D(facts):
  181. kb = FactKB(f)
  182. kb.deduce_all_facts(facts)
  183. return kb
  184. assert D({'real': T}) == {'real': T}
  185. assert D({'real': F}) == {'real': F, 'pos': F, 'npos': F}
  186. assert D({'pos': T}) == {'real': T, 'pos': T}
  187. assert D({'npos': T}) == {'real': T, 'npos': T}
  188. # --- key tests below ---
  189. assert D({'pos': F, 'npos': F}) == {'real': F, 'pos': F, 'npos': F}
  190. assert D({'real': T, 'pos': F}) == {'real': T, 'pos': F, 'npos': T}
  191. assert D({'real': T, 'npos': F}) == {'real': T, 'pos': T, 'npos': F}
  192. assert D({'pos': T, 'npos': F}) == {'real': T, 'pos': T, 'npos': F}
  193. assert D({'pos': F, 'npos': T}) == {'real': T, 'pos': F, 'npos': T}
  194. def test_FactRules_deduce_multiple2():
  195. f = FactRules(['real == neg | zero | pos'])
  196. def D(facts):
  197. kb = FactKB(f)
  198. kb.deduce_all_facts(facts)
  199. return kb
  200. assert D({'real': T}) == {'real': T}
  201. assert D({'real': F}) == {'real': F, 'neg': F, 'zero': F, 'pos': F}
  202. assert D({'neg': T}) == {'real': T, 'neg': T}
  203. assert D({'zero': T}) == {'real': T, 'zero': T}
  204. assert D({'pos': T}) == {'real': T, 'pos': T}
  205. # --- key tests below ---
  206. assert D({'neg': F, 'zero': F, 'pos': F}) == {'real': F, 'neg': F,
  207. 'zero': F, 'pos': F}
  208. assert D({'real': T, 'neg': F}) == {'real': T, 'neg': F}
  209. assert D({'real': T, 'zero': F}) == {'real': T, 'zero': F}
  210. assert D({'real': T, 'pos': F}) == {'real': T, 'pos': F}
  211. assert D({'real': T, 'zero': F, 'pos': F}) == {'real': T,
  212. 'neg': T, 'zero': F, 'pos': F}
  213. assert D({'real': T, 'neg': F, 'pos': F}) == {'real': T,
  214. 'neg': F, 'zero': T, 'pos': F}
  215. assert D({'real': T, 'neg': F, 'zero': F }) == {'real': T,
  216. 'neg': F, 'zero': F, 'pos': T}
  217. assert D({'neg': T, 'zero': F, 'pos': F}) == {'real': T, 'neg': T,
  218. 'zero': F, 'pos': F}
  219. assert D({'neg': F, 'zero': T, 'pos': F}) == {'real': T, 'neg': F,
  220. 'zero': T, 'pos': F}
  221. assert D({'neg': F, 'zero': F, 'pos': T}) == {'real': T, 'neg': F,
  222. 'zero': F, 'pos': T}
  223. def test_FactRules_deduce_base():
  224. # deduction that starts from base
  225. f = FactRules(['real == neg | zero | pos',
  226. 'neg -> real & !zero & !pos',
  227. 'pos -> real & !zero & !neg'])
  228. base = FactKB(f)
  229. base.deduce_all_facts({'real': T, 'neg': F})
  230. assert base == {'real': T, 'neg': F}
  231. base.deduce_all_facts({'zero': F})
  232. assert base == {'real': T, 'neg': F, 'zero': F, 'pos': T}
  233. def test_FactRules_deduce_staticext():
  234. # verify that static beta-extensions deduction takes place
  235. f = FactRules(['real == neg | zero | pos',
  236. 'neg -> real & !zero & !pos',
  237. 'pos -> real & !zero & !neg',
  238. 'nneg == real & !neg',
  239. 'npos == real & !pos'])
  240. assert ('npos', True) in f.full_implications[('neg', True)]
  241. assert ('nneg', True) in f.full_implications[('pos', True)]
  242. assert ('nneg', True) in f.full_implications[('zero', True)]
  243. assert ('npos', True) in f.full_implications[('zero', True)]