ask.py 18 KB

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