Coverage for /usr/lib/python3/dist-packages/sympy/assumptions/assume.py: 34%
177 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""A module which implements predicates and assumption context."""
3from contextlib import contextmanager
4import inspect
5from sympy.core.symbol import Str
6from sympy.core.sympify import _sympify
7from sympy.logic.boolalg import Boolean, false, true
8from sympy.multipledispatch.dispatcher import Dispatcher, str_signature
9from sympy.utilities.exceptions import sympy_deprecation_warning
10from sympy.utilities.iterables import is_sequence
11from sympy.utilities.source import get_class
14class AssumptionsContext(set):
15 """
16 Set containing default assumptions which are applied to the ``ask()``
17 function.
19 Explanation
20 ===========
22 This is used to represent global assumptions, but you can also use this
23 class to create your own local assumptions contexts. It is basically a thin
24 wrapper to Python's set, so see its documentation for advanced usage.
26 Examples
27 ========
29 The default assumption context is ``global_assumptions``, which is initially empty:
31 >>> from sympy import ask, Q
32 >>> from sympy.assumptions import global_assumptions
33 >>> global_assumptions
34 AssumptionsContext()
36 You can add default assumptions:
38 >>> from sympy.abc import x
39 >>> global_assumptions.add(Q.real(x))
40 >>> global_assumptions
41 AssumptionsContext({Q.real(x)})
42 >>> ask(Q.real(x))
43 True
45 And remove them:
47 >>> global_assumptions.remove(Q.real(x))
48 >>> print(ask(Q.real(x)))
49 None
51 The ``clear()`` method removes every assumption:
53 >>> global_assumptions.add(Q.positive(x))
54 >>> global_assumptions
55 AssumptionsContext({Q.positive(x)})
56 >>> global_assumptions.clear()
57 >>> global_assumptions
58 AssumptionsContext()
60 See Also
61 ========
63 assuming
65 """
67 def add(self, *assumptions):
68 """Add assumptions."""
69 for a in assumptions:
70 super().add(a)
72 def _sympystr(self, printer):
73 if not self:
74 return "%s()" % self.__class__.__name__
75 return "{}({})".format(self.__class__.__name__, printer._print_set(self))
77global_assumptions = AssumptionsContext()
80class AppliedPredicate(Boolean):
81 """
82 The class of expressions resulting from applying ``Predicate`` to
83 the arguments. ``AppliedPredicate`` merely wraps its argument and
84 remain unevaluated. To evaluate it, use the ``ask()`` function.
86 Examples
87 ========
89 >>> from sympy import Q, ask
90 >>> Q.integer(1)
91 Q.integer(1)
93 The ``function`` attribute returns the predicate, and the ``arguments``
94 attribute returns the tuple of arguments.
96 >>> type(Q.integer(1))
97 <class 'sympy.assumptions.assume.AppliedPredicate'>
98 >>> Q.integer(1).function
99 Q.integer
100 >>> Q.integer(1).arguments
101 (1,)
103 Applied predicates can be evaluated to a boolean value with ``ask``:
105 >>> ask(Q.integer(1))
106 True
108 """
109 __slots__ = ()
111 def __new__(cls, predicate, *args):
112 if not isinstance(predicate, Predicate):
113 raise TypeError("%s is not a Predicate." % predicate)
114 args = map(_sympify, args)
115 return super().__new__(cls, predicate, *args)
117 @property
118 def arg(self):
119 """
120 Return the expression used by this assumption.
122 Examples
123 ========
125 >>> from sympy import Q, Symbol
126 >>> x = Symbol('x')
127 >>> a = Q.integer(x + 1)
128 >>> a.arg
129 x + 1
131 """
132 # Will be deprecated
133 args = self._args
134 if len(args) == 2:
135 # backwards compatibility
136 return args[1]
137 raise TypeError("'arg' property is allowed only for unary predicates.")
139 @property
140 def function(self):
141 """
142 Return the predicate.
143 """
144 # Will be changed to self.args[0] after args overriding is removed
145 return self._args[0]
147 @property
148 def arguments(self):
149 """
150 Return the arguments which are applied to the predicate.
151 """
152 # Will be changed to self.args[1:] after args overriding is removed
153 return self._args[1:]
155 def _eval_ask(self, assumptions):
156 return self.function.eval(self.arguments, assumptions)
158 @property
159 def binary_symbols(self):
160 from .ask import Q
161 if self.function == Q.is_true:
162 i = self.arguments[0]
163 if i.is_Boolean or i.is_Symbol:
164 return i.binary_symbols
165 if self.function in (Q.eq, Q.ne):
166 if true in self.arguments or false in self.arguments:
167 if self.arguments[0].is_Symbol:
168 return {self.arguments[0]}
169 elif self.arguments[1].is_Symbol:
170 return {self.arguments[1]}
171 return set()
174class PredicateMeta(type):
175 def __new__(cls, clsname, bases, dct):
176 # If handler is not defined, assign empty dispatcher.
177 if "handler" not in dct:
178 name = f"Ask{clsname.capitalize()}Handler"
179 handler = Dispatcher(name, doc="Handler for key %s" % name)
180 dct["handler"] = handler
182 dct["_orig_doc"] = dct.get("__doc__", "")
184 return super().__new__(cls, clsname, bases, dct)
186 @property
187 def __doc__(cls):
188 handler = cls.handler
189 doc = cls._orig_doc
190 if cls is not Predicate and handler is not None:
191 doc += "Handler\n"
192 doc += " =======\n\n"
194 # Append the handler's doc without breaking sphinx documentation.
195 docs = [" Multiply dispatched method: %s" % handler.name]
196 if handler.doc:
197 for line in handler.doc.splitlines():
198 if not line:
199 continue
200 docs.append(" %s" % line)
201 other = []
202 for sig in handler.ordering[::-1]:
203 func = handler.funcs[sig]
204 if func.__doc__:
205 s = ' Inputs: <%s>' % str_signature(sig)
206 lines = []
207 for line in func.__doc__.splitlines():
208 lines.append(" %s" % line)
209 s += "\n".join(lines)
210 docs.append(s)
211 else:
212 other.append(str_signature(sig))
213 if other:
214 othersig = " Other signatures:"
215 for line in other:
216 othersig += "\n * %s" % line
217 docs.append(othersig)
219 doc += '\n\n'.join(docs)
221 return doc
224class Predicate(Boolean, metaclass=PredicateMeta):
225 """
226 Base class for mathematical predicates. It also serves as a
227 constructor for undefined predicate objects.
229 Explanation
230 ===========
232 Predicate is a function that returns a boolean value [1].
234 Predicate function is object, and it is instance of predicate class.
235 When a predicate is applied to arguments, ``AppliedPredicate``
236 instance is returned. This merely wraps the argument and remain
237 unevaluated. To obtain the truth value of applied predicate, use the
238 function ``ask``.
240 Evaluation of predicate is done by multiple dispatching. You can
241 register new handler to the predicate to support new types.
243 Every predicate in SymPy can be accessed via the property of ``Q``.
244 For example, ``Q.even`` returns the predicate which checks if the
245 argument is even number.
247 To define a predicate which can be evaluated, you must subclass this
248 class, make an instance of it, and register it to ``Q``. After then,
249 dispatch the handler by argument types.
251 If you directly construct predicate using this class, you will get
252 ``UndefinedPredicate`` which cannot be dispatched. This is useful
253 when you are building boolean expressions which do not need to be
254 evaluated.
256 Examples
257 ========
259 Applying and evaluating to boolean value:
261 >>> from sympy import Q, ask
262 >>> ask(Q.prime(7))
263 True
265 You can define a new predicate by subclassing and dispatching. Here,
266 we define a predicate for sexy primes [2] as an example.
268 >>> from sympy import Predicate, Integer
269 >>> class SexyPrimePredicate(Predicate):
270 ... name = "sexyprime"
271 >>> Q.sexyprime = SexyPrimePredicate()
272 >>> @Q.sexyprime.register(Integer, Integer)
273 ... def _(int1, int2, assumptions):
274 ... args = sorted([int1, int2])
275 ... if not all(ask(Q.prime(a), assumptions) for a in args):
276 ... return False
277 ... return args[1] - args[0] == 6
278 >>> ask(Q.sexyprime(5, 11))
279 True
281 Direct constructing returns ``UndefinedPredicate``, which can be
282 applied but cannot be dispatched.
284 >>> from sympy import Predicate, Integer
285 >>> Q.P = Predicate("P")
286 >>> type(Q.P)
287 <class 'sympy.assumptions.assume.UndefinedPredicate'>
288 >>> Q.P(1)
289 Q.P(1)
290 >>> Q.P.register(Integer)(lambda expr, assump: True)
291 Traceback (most recent call last):
292 ...
293 TypeError: <class 'sympy.assumptions.assume.UndefinedPredicate'> cannot be dispatched.
295 References
296 ==========
298 .. [1] https://en.wikipedia.org/wiki/Predicate_%28mathematical_logic%29
299 .. [2] https://en.wikipedia.org/wiki/Sexy_prime
301 """
303 is_Atom = True
305 def __new__(cls, *args, **kwargs):
306 if cls is Predicate:
307 return UndefinedPredicate(*args, **kwargs)
308 obj = super().__new__(cls, *args)
309 return obj
311 @property
312 def name(self):
313 # May be overridden
314 return type(self).__name__
316 @classmethod
317 def register(cls, *types, **kwargs):
318 """
319 Register the signature to the handler.
320 """
321 if cls.handler is None:
322 raise TypeError("%s cannot be dispatched." % type(cls))
323 return cls.handler.register(*types, **kwargs)
325 @classmethod
326 def register_many(cls, *types, **kwargs):
327 """
328 Register multiple signatures to same handler.
329 """
330 def _(func):
331 for t in types:
332 if not is_sequence(t):
333 t = (t,) # for convenience, allow passing `type` to mean `(type,)`
334 cls.register(*t, **kwargs)(func)
335 return _
337 def __call__(self, *args):
338 return AppliedPredicate(self, *args)
340 def eval(self, args, assumptions=True):
341 """
342 Evaluate ``self(*args)`` under the given assumptions.
344 This uses only direct resolution methods, not logical inference.
345 """
346 result = None
347 try:
348 result = self.handler(*args, assumptions=assumptions)
349 except NotImplementedError:
350 pass
351 return result
353 def _eval_refine(self, assumptions):
354 # When Predicate is no longer Boolean, delete this method
355 return self
358class UndefinedPredicate(Predicate):
359 """
360 Predicate without handler.
362 Explanation
363 ===========
365 This predicate is generated by using ``Predicate`` directly for
366 construction. It does not have a handler, and evaluating this with
367 arguments is done by SAT solver.
369 Examples
370 ========
372 >>> from sympy import Predicate, Q
373 >>> Q.P = Predicate('P')
374 >>> Q.P.func
375 <class 'sympy.assumptions.assume.UndefinedPredicate'>
376 >>> Q.P.name
377 Str('P')
379 """
381 handler = None
383 def __new__(cls, name, handlers=None):
384 # "handlers" parameter supports old design
385 if not isinstance(name, Str):
386 name = Str(name)
387 obj = super(Boolean, cls).__new__(cls, name)
388 obj.handlers = handlers or []
389 return obj
391 @property
392 def name(self):
393 return self.args[0]
395 def _hashable_content(self):
396 return (self.name,)
398 def __getnewargs__(self):
399 return (self.name,)
401 def __call__(self, expr):
402 return AppliedPredicate(self, expr)
404 def add_handler(self, handler):
405 sympy_deprecation_warning(
406 """
407 The AskHandler system is deprecated. Predicate.add_handler()
408 should be replaced with the multipledispatch handler of Predicate.
409 """,
410 deprecated_since_version="1.8",
411 active_deprecations_target='deprecated-askhandler',
412 )
413 self.handlers.append(handler)
415 def remove_handler(self, handler):
416 sympy_deprecation_warning(
417 """
418 The AskHandler system is deprecated. Predicate.remove_handler()
419 should be replaced with the multipledispatch handler of Predicate.
420 """,
421 deprecated_since_version="1.8",
422 active_deprecations_target='deprecated-askhandler',
423 )
424 self.handlers.remove(handler)
426 def eval(self, args, assumptions=True):
427 # Support for deprecated design
428 # When old design is removed, this will always return None
429 sympy_deprecation_warning(
430 """
431 The AskHandler system is deprecated. Evaluating UndefinedPredicate
432 objects should be replaced with the multipledispatch handler of
433 Predicate.
434 """,
435 deprecated_since_version="1.8",
436 active_deprecations_target='deprecated-askhandler',
437 stacklevel=5,
438 )
439 expr, = args
440 res, _res = None, None
441 mro = inspect.getmro(type(expr))
442 for handler in self.handlers:
443 cls = get_class(handler)
444 for subclass in mro:
445 eval_ = getattr(cls, subclass.__name__, None)
446 if eval_ is None:
447 continue
448 res = eval_(expr, assumptions)
449 # Do not stop if value returned is None
450 # Try to check for higher classes
451 if res is None:
452 continue
453 if _res is None:
454 _res = res
455 else:
456 # only check consistency if both resolutors have concluded
457 if _res != res:
458 raise ValueError('incompatible resolutors')
459 break
460 return res
463@contextmanager
464def assuming(*assumptions):
465 """
466 Context manager for assumptions.
468 Examples
469 ========
471 >>> from sympy import assuming, Q, ask
472 >>> from sympy.abc import x, y
473 >>> print(ask(Q.integer(x + y)))
474 None
475 >>> with assuming(Q.integer(x), Q.integer(y)):
476 ... print(ask(Q.integer(x + y)))
477 True
478 """
479 old_global_assumptions = global_assumptions.copy()
480 global_assumptions.update(assumptions)
481 try:
482 yield
483 finally:
484 global_assumptions.clear()
485 global_assumptions.update(old_global_assumptions)