inference.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. """Inference in propositional logic"""
  2. from sympy.logic.boolalg import And, Not, conjuncts, to_cnf, BooleanFunction
  3. from sympy.core.sorting import ordered
  4. from sympy.core.sympify import sympify
  5. from sympy.external.importtools import import_module
  6. def literal_symbol(literal):
  7. """
  8. The symbol in this literal (without the negation).
  9. Examples
  10. ========
  11. >>> from sympy.abc import A
  12. >>> from sympy.logic.inference import literal_symbol
  13. >>> literal_symbol(A)
  14. A
  15. >>> literal_symbol(~A)
  16. A
  17. """
  18. if literal is True or literal is False:
  19. return literal
  20. try:
  21. if literal.is_Symbol:
  22. return literal
  23. if literal.is_Not:
  24. return literal_symbol(literal.args[0])
  25. else:
  26. raise ValueError
  27. except (AttributeError, ValueError):
  28. raise ValueError("Argument must be a boolean literal.")
  29. def satisfiable(expr, algorithm=None, all_models=False, minimal=False):
  30. """
  31. Check satisfiability of a propositional sentence.
  32. Returns a model when it succeeds.
  33. Returns {true: true} for trivially true expressions.
  34. On setting all_models to True, if given expr is satisfiable then
  35. returns a generator of models. However, if expr is unsatisfiable
  36. then returns a generator containing the single element False.
  37. Examples
  38. ========
  39. >>> from sympy.abc import A, B
  40. >>> from sympy.logic.inference import satisfiable
  41. >>> satisfiable(A & ~B)
  42. {A: True, B: False}
  43. >>> satisfiable(A & ~A)
  44. False
  45. >>> satisfiable(True)
  46. {True: True}
  47. >>> next(satisfiable(A & ~A, all_models=True))
  48. False
  49. >>> models = satisfiable((A >> B) & B, all_models=True)
  50. >>> next(models)
  51. {A: False, B: True}
  52. >>> next(models)
  53. {A: True, B: True}
  54. >>> def use_models(models):
  55. ... for model in models:
  56. ... if model:
  57. ... # Do something with the model.
  58. ... print(model)
  59. ... else:
  60. ... # Given expr is unsatisfiable.
  61. ... print("UNSAT")
  62. >>> use_models(satisfiable(A >> ~A, all_models=True))
  63. {A: False}
  64. >>> use_models(satisfiable(A ^ A, all_models=True))
  65. UNSAT
  66. """
  67. if algorithm is None or algorithm == "pycosat":
  68. pycosat = import_module('pycosat')
  69. if pycosat is not None:
  70. algorithm = "pycosat"
  71. else:
  72. if algorithm == "pycosat":
  73. raise ImportError("pycosat module is not present")
  74. # Silently fall back to dpll2 if pycosat
  75. # is not installed
  76. algorithm = "dpll2"
  77. if algorithm=="minisat22":
  78. pysat = import_module('pysat')
  79. if pysat is None:
  80. algorithm = "dpll2"
  81. if algorithm == "dpll":
  82. from sympy.logic.algorithms.dpll import dpll_satisfiable
  83. return dpll_satisfiable(expr)
  84. elif algorithm == "dpll2":
  85. from sympy.logic.algorithms.dpll2 import dpll_satisfiable
  86. return dpll_satisfiable(expr, all_models)
  87. elif algorithm == "pycosat":
  88. from sympy.logic.algorithms.pycosat_wrapper import pycosat_satisfiable
  89. return pycosat_satisfiable(expr, all_models)
  90. elif algorithm == "minisat22":
  91. from sympy.logic.algorithms.minisat22_wrapper import minisat22_satisfiable
  92. return minisat22_satisfiable(expr, all_models, minimal)
  93. raise NotImplementedError
  94. def valid(expr):
  95. """
  96. Check validity of a propositional sentence.
  97. A valid propositional sentence is True under every assignment.
  98. Examples
  99. ========
  100. >>> from sympy.abc import A, B
  101. >>> from sympy.logic.inference import valid
  102. >>> valid(A | ~A)
  103. True
  104. >>> valid(A | B)
  105. False
  106. References
  107. ==========
  108. .. [1] https://en.wikipedia.org/wiki/Validity
  109. """
  110. return not satisfiable(Not(expr))
  111. def pl_true(expr, model=None, deep=False):
  112. """
  113. Returns whether the given assignment is a model or not.
  114. If the assignment does not specify the value for every proposition,
  115. this may return None to indicate 'not obvious'.
  116. Parameters
  117. ==========
  118. model : dict, optional, default: {}
  119. Mapping of symbols to boolean values to indicate assignment.
  120. deep: boolean, optional, default: False
  121. Gives the value of the expression under partial assignments
  122. correctly. May still return None to indicate 'not obvious'.
  123. Examples
  124. ========
  125. >>> from sympy.abc import A, B
  126. >>> from sympy.logic.inference import pl_true
  127. >>> pl_true( A & B, {A: True, B: True})
  128. True
  129. >>> pl_true(A & B, {A: False})
  130. False
  131. >>> pl_true(A & B, {A: True})
  132. >>> pl_true(A & B, {A: True}, deep=True)
  133. >>> pl_true(A >> (B >> A))
  134. >>> pl_true(A >> (B >> A), deep=True)
  135. True
  136. >>> pl_true(A & ~A)
  137. >>> pl_true(A & ~A, deep=True)
  138. False
  139. >>> pl_true(A & B & (~A | ~B), {A: True})
  140. >>> pl_true(A & B & (~A | ~B), {A: True}, deep=True)
  141. False
  142. """
  143. from sympy.core.symbol import Symbol
  144. boolean = (True, False)
  145. def _validate(expr):
  146. if isinstance(expr, Symbol) or expr in boolean:
  147. return True
  148. if not isinstance(expr, BooleanFunction):
  149. return False
  150. return all(_validate(arg) for arg in expr.args)
  151. if expr in boolean:
  152. return expr
  153. expr = sympify(expr)
  154. if not _validate(expr):
  155. raise ValueError("%s is not a valid boolean expression" % expr)
  156. if not model:
  157. model = {}
  158. model = {k: v for k, v in model.items() if v in boolean}
  159. result = expr.subs(model)
  160. if result in boolean:
  161. return bool(result)
  162. if deep:
  163. model = {k: True for k in result.atoms()}
  164. if pl_true(result, model):
  165. if valid(result):
  166. return True
  167. else:
  168. if not satisfiable(result):
  169. return False
  170. return None
  171. def entails(expr, formula_set=None):
  172. """
  173. Check whether the given expr_set entail an expr.
  174. If formula_set is empty then it returns the validity of expr.
  175. Examples
  176. ========
  177. >>> from sympy.abc import A, B, C
  178. >>> from sympy.logic.inference import entails
  179. >>> entails(A, [A >> B, B >> C])
  180. False
  181. >>> entails(C, [A >> B, B >> C, A])
  182. True
  183. >>> entails(A >> B)
  184. False
  185. >>> entails(A >> (B >> A))
  186. True
  187. References
  188. ==========
  189. .. [1] https://en.wikipedia.org/wiki/Logical_consequence
  190. """
  191. if formula_set:
  192. formula_set = list(formula_set)
  193. else:
  194. formula_set = []
  195. formula_set.append(Not(expr))
  196. return not satisfiable(And(*formula_set))
  197. class KB:
  198. """Base class for all knowledge bases"""
  199. def __init__(self, sentence=None):
  200. self.clauses_ = set()
  201. if sentence:
  202. self.tell(sentence)
  203. def tell(self, sentence):
  204. raise NotImplementedError
  205. def ask(self, query):
  206. raise NotImplementedError
  207. def retract(self, sentence):
  208. raise NotImplementedError
  209. @property
  210. def clauses(self):
  211. return list(ordered(self.clauses_))
  212. class PropKB(KB):
  213. """A KB for Propositional Logic. Inefficient, with no indexing."""
  214. def tell(self, sentence):
  215. """Add the sentence's clauses to the KB
  216. Examples
  217. ========
  218. >>> from sympy.logic.inference import PropKB
  219. >>> from sympy.abc import x, y
  220. >>> l = PropKB()
  221. >>> l.clauses
  222. []
  223. >>> l.tell(x | y)
  224. >>> l.clauses
  225. [x | y]
  226. >>> l.tell(y)
  227. >>> l.clauses
  228. [y, x | y]
  229. """
  230. for c in conjuncts(to_cnf(sentence)):
  231. self.clauses_.add(c)
  232. def ask(self, query):
  233. """Checks if the query is true given the set of clauses.
  234. Examples
  235. ========
  236. >>> from sympy.logic.inference import PropKB
  237. >>> from sympy.abc import x, y
  238. >>> l = PropKB()
  239. >>> l.tell(x & ~y)
  240. >>> l.ask(x)
  241. True
  242. >>> l.ask(y)
  243. False
  244. """
  245. return entails(query, self.clauses_)
  246. def retract(self, sentence):
  247. """Remove the sentence's clauses from the KB
  248. Examples
  249. ========
  250. >>> from sympy.logic.inference import PropKB
  251. >>> from sympy.abc import x, y
  252. >>> l = PropKB()
  253. >>> l.clauses
  254. []
  255. >>> l.tell(x | y)
  256. >>> l.clauses
  257. [x | y]
  258. >>> l.retract(x | y)
  259. >>> l.clauses
  260. []
  261. """
  262. for c in conjuncts(to_cnf(sentence)):
  263. self.clauses_.discard(c)