ask.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. """Module for querying SymPy objects about assumptions."""
  2. from sympy.assumptions.assume import (global_assumptions, Predicate,
  3. AppliedPredicate)
  4. from sympy.assumptions.cnf import CNF, EncodedCNF, Literal
  5. from sympy.core import sympify
  6. from sympy.core.kind import BooleanKind
  7. from sympy.core.relational import Eq, Ne, Gt, Lt, Ge, Le
  8. from sympy.logic.inference import satisfiable
  9. from sympy.utilities.decorator import memoize_property
  10. from sympy.utilities.exceptions import (sympy_deprecation_warning,
  11. SymPyDeprecationWarning,
  12. ignore_warnings)
  13. # Memoization is necessary for the properties of AssumptionKeys to
  14. # ensure that only one object of Predicate objects are created.
  15. # This is because assumption handlers are registered on those objects.
  16. class AssumptionKeys:
  17. """
  18. This class contains all the supported keys by ``ask``.
  19. It should be accessed via the instance ``sympy.Q``.
  20. """
  21. # DO NOT add methods or properties other than predicate keys.
  22. # SAT solver checks the properties of Q and use them to compute the
  23. # fact system. Non-predicate attributes will break this.
  24. @memoize_property
  25. def hermitian(self):
  26. from .handlers.sets import HermitianPredicate
  27. return HermitianPredicate()
  28. @memoize_property
  29. def antihermitian(self):
  30. from .handlers.sets import AntihermitianPredicate
  31. return AntihermitianPredicate()
  32. @memoize_property
  33. def real(self):
  34. from .handlers.sets import RealPredicate
  35. return RealPredicate()
  36. @memoize_property
  37. def extended_real(self):
  38. from .handlers.sets import ExtendedRealPredicate
  39. return ExtendedRealPredicate()
  40. @memoize_property
  41. def imaginary(self):
  42. from .handlers.sets import ImaginaryPredicate
  43. return ImaginaryPredicate()
  44. @memoize_property
  45. def complex(self):
  46. from .handlers.sets import ComplexPredicate
  47. return ComplexPredicate()
  48. @memoize_property
  49. def algebraic(self):
  50. from .handlers.sets import AlgebraicPredicate
  51. return AlgebraicPredicate()
  52. @memoize_property
  53. def transcendental(self):
  54. from .predicates.sets import TranscendentalPredicate
  55. return TranscendentalPredicate()
  56. @memoize_property
  57. def integer(self):
  58. from .handlers.sets import IntegerPredicate
  59. return IntegerPredicate()
  60. @memoize_property
  61. def noninteger(self):
  62. from .predicates.sets import NonIntegerPredicate
  63. return NonIntegerPredicate()
  64. @memoize_property
  65. def rational(self):
  66. from .handlers.sets import RationalPredicate
  67. return RationalPredicate()
  68. @memoize_property
  69. def irrational(self):
  70. from .handlers.sets import IrrationalPredicate
  71. return IrrationalPredicate()
  72. @memoize_property
  73. def finite(self):
  74. from .handlers.calculus import FinitePredicate
  75. return FinitePredicate()
  76. @memoize_property
  77. def infinite(self):
  78. from .handlers.calculus import InfinitePredicate
  79. return InfinitePredicate()
  80. @memoize_property
  81. def positive_infinite(self):
  82. from .handlers.calculus import PositiveInfinitePredicate
  83. return PositiveInfinitePredicate()
  84. @memoize_property
  85. def negative_infinite(self):
  86. from .handlers.calculus import NegativeInfinitePredicate
  87. return NegativeInfinitePredicate()
  88. @memoize_property
  89. def positive(self):
  90. from .handlers.order import PositivePredicate
  91. return PositivePredicate()
  92. @memoize_property
  93. def negative(self):
  94. from .handlers.order import NegativePredicate
  95. return NegativePredicate()
  96. @memoize_property
  97. def zero(self):
  98. from .handlers.order import ZeroPredicate
  99. return ZeroPredicate()
  100. @memoize_property
  101. def extended_positive(self):
  102. from .handlers.order import ExtendedPositivePredicate
  103. return ExtendedPositivePredicate()
  104. @memoize_property
  105. def extended_negative(self):
  106. from .handlers.order import ExtendedNegativePredicate
  107. return ExtendedNegativePredicate()
  108. @memoize_property
  109. def nonzero(self):
  110. from .handlers.order import NonZeroPredicate
  111. return NonZeroPredicate()
  112. @memoize_property
  113. def nonpositive(self):
  114. from .handlers.order import NonPositivePredicate
  115. return NonPositivePredicate()
  116. @memoize_property
  117. def nonnegative(self):
  118. from .handlers.order import NonNegativePredicate
  119. return NonNegativePredicate()
  120. @memoize_property
  121. def extended_nonzero(self):
  122. from .handlers.order import ExtendedNonZeroPredicate
  123. return ExtendedNonZeroPredicate()
  124. @memoize_property
  125. def extended_nonpositive(self):
  126. from .handlers.order import ExtendedNonPositivePredicate
  127. return ExtendedNonPositivePredicate()
  128. @memoize_property
  129. def extended_nonnegative(self):
  130. from .handlers.order import ExtendedNonNegativePredicate
  131. return ExtendedNonNegativePredicate()
  132. @memoize_property
  133. def even(self):
  134. from .handlers.ntheory import EvenPredicate
  135. return EvenPredicate()
  136. @memoize_property
  137. def odd(self):
  138. from .handlers.ntheory import OddPredicate
  139. return OddPredicate()
  140. @memoize_property
  141. def prime(self):
  142. from .handlers.ntheory import PrimePredicate
  143. return PrimePredicate()
  144. @memoize_property
  145. def composite(self):
  146. from .handlers.ntheory import CompositePredicate
  147. return CompositePredicate()
  148. @memoize_property
  149. def commutative(self):
  150. from .handlers.common import CommutativePredicate
  151. return CommutativePredicate()
  152. @memoize_property
  153. def is_true(self):
  154. from .handlers.common import IsTruePredicate
  155. return IsTruePredicate()
  156. @memoize_property
  157. def symmetric(self):
  158. from .handlers.matrices import SymmetricPredicate
  159. return SymmetricPredicate()
  160. @memoize_property
  161. def invertible(self):
  162. from .handlers.matrices import InvertiblePredicate
  163. return InvertiblePredicate()
  164. @memoize_property
  165. def orthogonal(self):
  166. from .handlers.matrices import OrthogonalPredicate
  167. return OrthogonalPredicate()
  168. @memoize_property
  169. def unitary(self):
  170. from .handlers.matrices import UnitaryPredicate
  171. return UnitaryPredicate()
  172. @memoize_property
  173. def positive_definite(self):
  174. from .handlers.matrices import PositiveDefinitePredicate
  175. return PositiveDefinitePredicate()
  176. @memoize_property
  177. def upper_triangular(self):
  178. from .handlers.matrices import UpperTriangularPredicate
  179. return UpperTriangularPredicate()
  180. @memoize_property
  181. def lower_triangular(self):
  182. from .handlers.matrices import LowerTriangularPredicate
  183. return LowerTriangularPredicate()
  184. @memoize_property
  185. def diagonal(self):
  186. from .handlers.matrices import DiagonalPredicate
  187. return DiagonalPredicate()
  188. @memoize_property
  189. def fullrank(self):
  190. from .handlers.matrices import FullRankPredicate
  191. return FullRankPredicate()
  192. @memoize_property
  193. def square(self):
  194. from .handlers.matrices import SquarePredicate
  195. return SquarePredicate()
  196. @memoize_property
  197. def integer_elements(self):
  198. from .handlers.matrices import IntegerElementsPredicate
  199. return IntegerElementsPredicate()
  200. @memoize_property
  201. def real_elements(self):
  202. from .handlers.matrices import RealElementsPredicate
  203. return RealElementsPredicate()
  204. @memoize_property
  205. def complex_elements(self):
  206. from .handlers.matrices import ComplexElementsPredicate
  207. return ComplexElementsPredicate()
  208. @memoize_property
  209. def singular(self):
  210. from .predicates.matrices import SingularPredicate
  211. return SingularPredicate()
  212. @memoize_property
  213. def normal(self):
  214. from .predicates.matrices import NormalPredicate
  215. return NormalPredicate()
  216. @memoize_property
  217. def triangular(self):
  218. from .predicates.matrices import TriangularPredicate
  219. return TriangularPredicate()
  220. @memoize_property
  221. def unit_triangular(self):
  222. from .predicates.matrices import UnitTriangularPredicate
  223. return UnitTriangularPredicate()
  224. @memoize_property
  225. def eq(self):
  226. from .relation.equality import EqualityPredicate
  227. return EqualityPredicate()
  228. @memoize_property
  229. def ne(self):
  230. from .relation.equality import UnequalityPredicate
  231. return UnequalityPredicate()
  232. @memoize_property
  233. def gt(self):
  234. from .relation.equality import StrictGreaterThanPredicate
  235. return StrictGreaterThanPredicate()
  236. @memoize_property
  237. def ge(self):
  238. from .relation.equality import GreaterThanPredicate
  239. return GreaterThanPredicate()
  240. @memoize_property
  241. def lt(self):
  242. from .relation.equality import StrictLessThanPredicate
  243. return StrictLessThanPredicate()
  244. @memoize_property
  245. def le(self):
  246. from .relation.equality import LessThanPredicate
  247. return LessThanPredicate()
  248. Q = AssumptionKeys()
  249. def _extract_all_facts(assump, exprs):
  250. """
  251. Extract all relevant assumptions from *assump* with respect to given *exprs*.
  252. Parameters
  253. ==========
  254. assump : sympy.assumptions.cnf.CNF
  255. exprs : tuple of expressions
  256. Returns
  257. =======
  258. sympy.assumptions.cnf.CNF
  259. Examples
  260. ========
  261. >>> from sympy import Q
  262. >>> from sympy.assumptions.cnf import CNF
  263. >>> from sympy.assumptions.ask import _extract_all_facts
  264. >>> from sympy.abc import x, y
  265. >>> assump = CNF.from_prop(Q.positive(x) & Q.integer(y))
  266. >>> exprs = (x,)
  267. >>> cnf = _extract_all_facts(assump, exprs)
  268. >>> cnf.clauses
  269. {frozenset({Literal(Q.positive, False)})}
  270. """
  271. facts = set()
  272. for clause in assump.clauses:
  273. args = []
  274. for literal in clause:
  275. if isinstance(literal.lit, AppliedPredicate) and len(literal.lit.arguments) == 1:
  276. if literal.lit.arg in exprs:
  277. # Add literal if it has matching in it
  278. args.append(Literal(literal.lit.function, literal.is_Not))
  279. else:
  280. # If any of the literals doesn't have matching expr don't add the whole clause.
  281. break
  282. else:
  283. # If any of the literals aren't unary predicate don't add the whole clause.
  284. break
  285. else:
  286. if args:
  287. facts.add(frozenset(args))
  288. return CNF(facts)
  289. def ask(proposition, assumptions=True, context=global_assumptions):
  290. """
  291. Function to evaluate the proposition with assumptions.
  292. Explanation
  293. ===========
  294. This function evaluates the proposition to ``True`` or ``False`` if
  295. the truth value can be determined. If not, it returns ``None``.
  296. It should be discerned from :func:`~.refine` which, when applied to a
  297. proposition, simplifies the argument to symbolic ``Boolean`` instead of
  298. Python built-in ``True``, ``False`` or ``None``.
  299. **Syntax**
  300. * ask(proposition)
  301. Evaluate the *proposition* in global assumption context.
  302. * ask(proposition, assumptions)
  303. Evaluate the *proposition* with respect to *assumptions* in
  304. global assumption context.
  305. Parameters
  306. ==========
  307. proposition : Boolean
  308. Proposition which will be evaluated to boolean value. If this is
  309. not ``AppliedPredicate``, it will be wrapped by ``Q.is_true``.
  310. assumptions : Boolean, optional
  311. Local assumptions to evaluate the *proposition*.
  312. context : AssumptionsContext, optional
  313. Default assumptions to evaluate the *proposition*. By default,
  314. this is ``sympy.assumptions.global_assumptions`` variable.
  315. Returns
  316. =======
  317. ``True``, ``False``, or ``None``
  318. Raises
  319. ======
  320. TypeError : *proposition* or *assumptions* is not valid logical expression.
  321. ValueError : assumptions are inconsistent.
  322. Examples
  323. ========
  324. >>> from sympy import ask, Q, pi
  325. >>> from sympy.abc import x, y
  326. >>> ask(Q.rational(pi))
  327. False
  328. >>> ask(Q.even(x*y), Q.even(x) & Q.integer(y))
  329. True
  330. >>> ask(Q.prime(4*x), Q.integer(x))
  331. False
  332. If the truth value cannot be determined, ``None`` will be returned.
  333. >>> print(ask(Q.odd(3*x))) # cannot determine unless we know x
  334. None
  335. ``ValueError`` is raised if assumptions are inconsistent.
  336. >>> ask(Q.integer(x), Q.even(x) & Q.odd(x))
  337. Traceback (most recent call last):
  338. ...
  339. ValueError: inconsistent assumptions Q.even(x) & Q.odd(x)
  340. Notes
  341. =====
  342. Relations in assumptions are not implemented (yet), so the following
  343. will not give a meaningful result.
  344. >>> ask(Q.positive(x), x > 0)
  345. It is however a work in progress.
  346. See Also
  347. ========
  348. sympy.assumptions.refine.refine : Simplification using assumptions.
  349. Proposition is not reduced to ``None`` if the truth value cannot
  350. be determined.
  351. """
  352. from sympy.assumptions.satask import satask
  353. from sympy.assumptions.lra_satask import lra_satask
  354. from sympy.logic.algorithms.lra_theory import UnhandledInput
  355. proposition = sympify(proposition)
  356. assumptions = sympify(assumptions)
  357. if isinstance(proposition, Predicate) or proposition.kind is not BooleanKind:
  358. raise TypeError("proposition must be a valid logical expression")
  359. if isinstance(assumptions, Predicate) or assumptions.kind is not BooleanKind:
  360. raise TypeError("assumptions must be a valid logical expression")
  361. binrelpreds = {Eq: Q.eq, Ne: Q.ne, Gt: Q.gt, Lt: Q.lt, Ge: Q.ge, Le: Q.le}
  362. if isinstance(proposition, AppliedPredicate):
  363. key, args = proposition.function, proposition.arguments
  364. elif proposition.func in binrelpreds:
  365. key, args = binrelpreds[type(proposition)], proposition.args
  366. else:
  367. key, args = Q.is_true, (proposition,)
  368. # convert local and global assumptions to CNF
  369. assump_cnf = CNF.from_prop(assumptions)
  370. assump_cnf.extend(context)
  371. # extract the relevant facts from assumptions with respect to args
  372. local_facts = _extract_all_facts(assump_cnf, args)
  373. # convert default facts and assumed facts to encoded CNF
  374. known_facts_cnf = get_all_known_facts()
  375. enc_cnf = EncodedCNF()
  376. enc_cnf.from_cnf(CNF(known_facts_cnf))
  377. enc_cnf.add_from_cnf(local_facts)
  378. # check the satisfiability of given assumptions
  379. if local_facts.clauses and satisfiable(enc_cnf) is False:
  380. raise ValueError("inconsistent assumptions %s" % assumptions)
  381. # quick computation for single fact
  382. res = _ask_single_fact(key, local_facts)
  383. if res is not None:
  384. return res
  385. # direct resolution method, no logic
  386. res = key(*args)._eval_ask(assumptions)
  387. if res is not None:
  388. return bool(res)
  389. # using satask (still costly)
  390. res = satask(proposition, assumptions=assumptions, context=context)
  391. if res is not None:
  392. return res
  393. try:
  394. res = lra_satask(proposition, assumptions=assumptions, context=context)
  395. except UnhandledInput:
  396. return None
  397. return res
  398. def _ask_single_fact(key, local_facts):
  399. """
  400. Compute the truth value of single predicate using assumptions.
  401. Parameters
  402. ==========
  403. key : sympy.assumptions.assume.Predicate
  404. Proposition predicate.
  405. local_facts : sympy.assumptions.cnf.CNF
  406. Local assumption in CNF form.
  407. Returns
  408. =======
  409. ``True``, ``False`` or ``None``
  410. Examples
  411. ========
  412. >>> from sympy import Q
  413. >>> from sympy.assumptions.cnf import CNF
  414. >>> from sympy.assumptions.ask import _ask_single_fact
  415. If prerequisite of proposition is rejected by the assumption,
  416. return ``False``.
  417. >>> key, assump = Q.zero, ~Q.zero
  418. >>> local_facts = CNF.from_prop(assump)
  419. >>> _ask_single_fact(key, local_facts)
  420. False
  421. >>> key, assump = Q.zero, ~Q.even
  422. >>> local_facts = CNF.from_prop(assump)
  423. >>> _ask_single_fact(key, local_facts)
  424. False
  425. If assumption implies the proposition, return ``True``.
  426. >>> key, assump = Q.even, Q.zero
  427. >>> local_facts = CNF.from_prop(assump)
  428. >>> _ask_single_fact(key, local_facts)
  429. True
  430. If proposition rejects the assumption, return ``False``.
  431. >>> key, assump = Q.even, Q.odd
  432. >>> local_facts = CNF.from_prop(assump)
  433. >>> _ask_single_fact(key, local_facts)
  434. False
  435. """
  436. if local_facts.clauses:
  437. known_facts_dict = get_known_facts_dict()
  438. if len(local_facts.clauses) == 1:
  439. cl, = local_facts.clauses
  440. if len(cl) == 1:
  441. f, = cl
  442. prop_facts = known_facts_dict.get(key, None)
  443. prop_req = prop_facts[0] if prop_facts is not None else set()
  444. if f.is_Not and f.arg in prop_req:
  445. # the prerequisite of proposition is rejected
  446. return False
  447. for clause in local_facts.clauses:
  448. if len(clause) == 1:
  449. f, = clause
  450. prop_facts = known_facts_dict.get(f.arg, None) if not f.is_Not else None
  451. if prop_facts is None:
  452. continue
  453. prop_req, prop_rej = prop_facts
  454. if key in prop_req:
  455. # assumption implies the proposition
  456. return True
  457. elif key in prop_rej:
  458. # proposition rejects the assumption
  459. return False
  460. return None
  461. def register_handler(key, handler):
  462. """
  463. Register a handler in the ask system. key must be a string and handler a
  464. class inheriting from AskHandler.
  465. .. deprecated:: 1.8.
  466. Use multipledispatch handler instead. See :obj:`~.Predicate`.
  467. """
  468. sympy_deprecation_warning(
  469. """
  470. The AskHandler system is deprecated. The register_handler() function
  471. should be replaced with the multipledispatch handler of Predicate.
  472. """,
  473. deprecated_since_version="1.8",
  474. active_deprecations_target='deprecated-askhandler',
  475. )
  476. if isinstance(key, Predicate):
  477. key = key.name.name
  478. Qkey = getattr(Q, key, None)
  479. if Qkey is not None:
  480. Qkey.add_handler(handler)
  481. else:
  482. setattr(Q, key, Predicate(key, handlers=[handler]))
  483. def remove_handler(key, handler):
  484. """
  485. Removes a handler from the ask system.
  486. .. deprecated:: 1.8.
  487. Use multipledispatch handler instead. See :obj:`~.Predicate`.
  488. """
  489. sympy_deprecation_warning(
  490. """
  491. The AskHandler system is deprecated. The remove_handler() function
  492. should be replaced with the multipledispatch handler of Predicate.
  493. """,
  494. deprecated_since_version="1.8",
  495. active_deprecations_target='deprecated-askhandler',
  496. )
  497. if isinstance(key, Predicate):
  498. key = key.name.name
  499. # Don't show the same warning again recursively
  500. with ignore_warnings(SymPyDeprecationWarning):
  501. getattr(Q, key).remove_handler(handler)
  502. from sympy.assumptions.ask_generated import (get_all_known_facts,
  503. get_known_facts_dict)