inference.py 8.8 KB

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