Spaces:
Sleeping
Sleeping
"""A module which implements predicates and assumption context.""" | |
from contextlib import contextmanager | |
import inspect | |
from sympy.core.symbol import Str | |
from sympy.core.sympify import _sympify | |
from sympy.logic.boolalg import Boolean, false, true | |
from sympy.multipledispatch.dispatcher import Dispatcher, str_signature | |
from sympy.utilities.exceptions import sympy_deprecation_warning | |
from sympy.utilities.iterables import is_sequence | |
from sympy.utilities.source import get_class | |
class AssumptionsContext(set): | |
""" | |
Set containing default assumptions which are applied to the ``ask()`` | |
function. | |
Explanation | |
=========== | |
This is used to represent global assumptions, but you can also use this | |
class to create your own local assumptions contexts. It is basically a thin | |
wrapper to Python's set, so see its documentation for advanced usage. | |
Examples | |
======== | |
The default assumption context is ``global_assumptions``, which is initially empty: | |
>>> from sympy import ask, Q | |
>>> from sympy.assumptions import global_assumptions | |
>>> global_assumptions | |
AssumptionsContext() | |
You can add default assumptions: | |
>>> from sympy.abc import x | |
>>> global_assumptions.add(Q.real(x)) | |
>>> global_assumptions | |
AssumptionsContext({Q.real(x)}) | |
>>> ask(Q.real(x)) | |
True | |
And remove them: | |
>>> global_assumptions.remove(Q.real(x)) | |
>>> print(ask(Q.real(x))) | |
None | |
The ``clear()`` method removes every assumption: | |
>>> global_assumptions.add(Q.positive(x)) | |
>>> global_assumptions | |
AssumptionsContext({Q.positive(x)}) | |
>>> global_assumptions.clear() | |
>>> global_assumptions | |
AssumptionsContext() | |
See Also | |
======== | |
assuming | |
""" | |
def add(self, *assumptions): | |
"""Add assumptions.""" | |
for a in assumptions: | |
super().add(a) | |
def _sympystr(self, printer): | |
if not self: | |
return "%s()" % self.__class__.__name__ | |
return "{}({})".format(self.__class__.__name__, printer._print_set(self)) | |
global_assumptions = AssumptionsContext() | |
class AppliedPredicate(Boolean): | |
""" | |
The class of expressions resulting from applying ``Predicate`` to | |
the arguments. ``AppliedPredicate`` merely wraps its argument and | |
remain unevaluated. To evaluate it, use the ``ask()`` function. | |
Examples | |
======== | |
>>> from sympy import Q, ask | |
>>> Q.integer(1) | |
Q.integer(1) | |
The ``function`` attribute returns the predicate, and the ``arguments`` | |
attribute returns the tuple of arguments. | |
>>> type(Q.integer(1)) | |
<class 'sympy.assumptions.assume.AppliedPredicate'> | |
>>> Q.integer(1).function | |
Q.integer | |
>>> Q.integer(1).arguments | |
(1,) | |
Applied predicates can be evaluated to a boolean value with ``ask``: | |
>>> ask(Q.integer(1)) | |
True | |
""" | |
__slots__ = () | |
def __new__(cls, predicate, *args): | |
if not isinstance(predicate, Predicate): | |
raise TypeError("%s is not a Predicate." % predicate) | |
args = map(_sympify, args) | |
return super().__new__(cls, predicate, *args) | |
def arg(self): | |
""" | |
Return the expression used by this assumption. | |
Examples | |
======== | |
>>> from sympy import Q, Symbol | |
>>> x = Symbol('x') | |
>>> a = Q.integer(x + 1) | |
>>> a.arg | |
x + 1 | |
""" | |
# Will be deprecated | |
args = self._args | |
if len(args) == 2: | |
# backwards compatibility | |
return args[1] | |
raise TypeError("'arg' property is allowed only for unary predicates.") | |
def function(self): | |
""" | |
Return the predicate. | |
""" | |
# Will be changed to self.args[0] after args overriding is removed | |
return self._args[0] | |
def arguments(self): | |
""" | |
Return the arguments which are applied to the predicate. | |
""" | |
# Will be changed to self.args[1:] after args overriding is removed | |
return self._args[1:] | |
def _eval_ask(self, assumptions): | |
return self.function.eval(self.arguments, assumptions) | |
def binary_symbols(self): | |
from .ask import Q | |
if self.function == Q.is_true: | |
i = self.arguments[0] | |
if i.is_Boolean or i.is_Symbol: | |
return i.binary_symbols | |
if self.function in (Q.eq, Q.ne): | |
if true in self.arguments or false in self.arguments: | |
if self.arguments[0].is_Symbol: | |
return {self.arguments[0]} | |
elif self.arguments[1].is_Symbol: | |
return {self.arguments[1]} | |
return set() | |
class PredicateMeta(type): | |
def __new__(cls, clsname, bases, dct): | |
# If handler is not defined, assign empty dispatcher. | |
if "handler" not in dct: | |
name = f"Ask{clsname.capitalize()}Handler" | |
handler = Dispatcher(name, doc="Handler for key %s" % name) | |
dct["handler"] = handler | |
dct["_orig_doc"] = dct.get("__doc__", "") | |
return super().__new__(cls, clsname, bases, dct) | |
def __doc__(cls): | |
handler = cls.handler | |
doc = cls._orig_doc | |
if cls is not Predicate and handler is not None: | |
doc += "Handler\n" | |
doc += " =======\n\n" | |
# Append the handler's doc without breaking sphinx documentation. | |
docs = [" Multiply dispatched method: %s" % handler.name] | |
if handler.doc: | |
for line in handler.doc.splitlines(): | |
if not line: | |
continue | |
docs.append(" %s" % line) | |
other = [] | |
for sig in handler.ordering[::-1]: | |
func = handler.funcs[sig] | |
if func.__doc__: | |
s = ' Inputs: <%s>' % str_signature(sig) | |
lines = [] | |
for line in func.__doc__.splitlines(): | |
lines.append(" %s" % line) | |
s += "\n".join(lines) | |
docs.append(s) | |
else: | |
other.append(str_signature(sig)) | |
if other: | |
othersig = " Other signatures:" | |
for line in other: | |
othersig += "\n * %s" % line | |
docs.append(othersig) | |
doc += '\n\n'.join(docs) | |
return doc | |
class Predicate(Boolean, metaclass=PredicateMeta): | |
""" | |
Base class for mathematical predicates. It also serves as a | |
constructor for undefined predicate objects. | |
Explanation | |
=========== | |
Predicate is a function that returns a boolean value [1]. | |
Predicate function is object, and it is instance of predicate class. | |
When a predicate is applied to arguments, ``AppliedPredicate`` | |
instance is returned. This merely wraps the argument and remain | |
unevaluated. To obtain the truth value of applied predicate, use the | |
function ``ask``. | |
Evaluation of predicate is done by multiple dispatching. You can | |
register new handler to the predicate to support new types. | |
Every predicate in SymPy can be accessed via the property of ``Q``. | |
For example, ``Q.even`` returns the predicate which checks if the | |
argument is even number. | |
To define a predicate which can be evaluated, you must subclass this | |
class, make an instance of it, and register it to ``Q``. After then, | |
dispatch the handler by argument types. | |
If you directly construct predicate using this class, you will get | |
``UndefinedPredicate`` which cannot be dispatched. This is useful | |
when you are building boolean expressions which do not need to be | |
evaluated. | |
Examples | |
======== | |
Applying and evaluating to boolean value: | |
>>> from sympy import Q, ask | |
>>> ask(Q.prime(7)) | |
True | |
You can define a new predicate by subclassing and dispatching. Here, | |
we define a predicate for sexy primes [2] as an example. | |
>>> from sympy import Predicate, Integer | |
>>> class SexyPrimePredicate(Predicate): | |
... name = "sexyprime" | |
>>> Q.sexyprime = SexyPrimePredicate() | |
>>> @Q.sexyprime.register(Integer, Integer) | |
... def _(int1, int2, assumptions): | |
... args = sorted([int1, int2]) | |
... if not all(ask(Q.prime(a), assumptions) for a in args): | |
... return False | |
... return args[1] - args[0] == 6 | |
>>> ask(Q.sexyprime(5, 11)) | |
True | |
Direct constructing returns ``UndefinedPredicate``, which can be | |
applied but cannot be dispatched. | |
>>> from sympy import Predicate, Integer | |
>>> Q.P = Predicate("P") | |
>>> type(Q.P) | |
<class 'sympy.assumptions.assume.UndefinedPredicate'> | |
>>> Q.P(1) | |
Q.P(1) | |
>>> Q.P.register(Integer)(lambda expr, assump: True) | |
Traceback (most recent call last): | |
... | |
TypeError: <class 'sympy.assumptions.assume.UndefinedPredicate'> cannot be dispatched. | |
References | |
========== | |
.. [1] https://en.wikipedia.org/wiki/Predicate_%28mathematical_logic%29 | |
.. [2] https://en.wikipedia.org/wiki/Sexy_prime | |
""" | |
is_Atom = True | |
def __new__(cls, *args, **kwargs): | |
if cls is Predicate: | |
return UndefinedPredicate(*args, **kwargs) | |
obj = super().__new__(cls, *args) | |
return obj | |
def name(self): | |
# May be overridden | |
return type(self).__name__ | |
def register(cls, *types, **kwargs): | |
""" | |
Register the signature to the handler. | |
""" | |
if cls.handler is None: | |
raise TypeError("%s cannot be dispatched." % type(cls)) | |
return cls.handler.register(*types, **kwargs) | |
def register_many(cls, *types, **kwargs): | |
""" | |
Register multiple signatures to same handler. | |
""" | |
def _(func): | |
for t in types: | |
if not is_sequence(t): | |
t = (t,) # for convenience, allow passing `type` to mean `(type,)` | |
cls.register(*t, **kwargs)(func) | |
return _ | |
def __call__(self, *args): | |
return AppliedPredicate(self, *args) | |
def eval(self, args, assumptions=True): | |
""" | |
Evaluate ``self(*args)`` under the given assumptions. | |
This uses only direct resolution methods, not logical inference. | |
""" | |
result = None | |
try: | |
result = self.handler(*args, assumptions=assumptions) | |
except NotImplementedError: | |
pass | |
return result | |
def _eval_refine(self, assumptions): | |
# When Predicate is no longer Boolean, delete this method | |
return self | |
class UndefinedPredicate(Predicate): | |
""" | |
Predicate without handler. | |
Explanation | |
=========== | |
This predicate is generated by using ``Predicate`` directly for | |
construction. It does not have a handler, and evaluating this with | |
arguments is done by SAT solver. | |
Examples | |
======== | |
>>> from sympy import Predicate, Q | |
>>> Q.P = Predicate('P') | |
>>> Q.P.func | |
<class 'sympy.assumptions.assume.UndefinedPredicate'> | |
>>> Q.P.name | |
Str('P') | |
""" | |
handler = None | |
def __new__(cls, name, handlers=None): | |
# "handlers" parameter supports old design | |
if not isinstance(name, Str): | |
name = Str(name) | |
obj = super(Boolean, cls).__new__(cls, name) | |
obj.handlers = handlers or [] | |
return obj | |
def name(self): | |
return self.args[0] | |
def _hashable_content(self): | |
return (self.name,) | |
def __getnewargs__(self): | |
return (self.name,) | |
def __call__(self, expr): | |
return AppliedPredicate(self, expr) | |
def add_handler(self, handler): | |
sympy_deprecation_warning( | |
""" | |
The AskHandler system is deprecated. Predicate.add_handler() | |
should be replaced with the multipledispatch handler of Predicate. | |
""", | |
deprecated_since_version="1.8", | |
active_deprecations_target='deprecated-askhandler', | |
) | |
self.handlers.append(handler) | |
def remove_handler(self, handler): | |
sympy_deprecation_warning( | |
""" | |
The AskHandler system is deprecated. Predicate.remove_handler() | |
should be replaced with the multipledispatch handler of Predicate. | |
""", | |
deprecated_since_version="1.8", | |
active_deprecations_target='deprecated-askhandler', | |
) | |
self.handlers.remove(handler) | |
def eval(self, args, assumptions=True): | |
# Support for deprecated design | |
# When old design is removed, this will always return None | |
sympy_deprecation_warning( | |
""" | |
The AskHandler system is deprecated. Evaluating UndefinedPredicate | |
objects should be replaced with the multipledispatch handler of | |
Predicate. | |
""", | |
deprecated_since_version="1.8", | |
active_deprecations_target='deprecated-askhandler', | |
stacklevel=5, | |
) | |
expr, = args | |
res, _res = None, None | |
mro = inspect.getmro(type(expr)) | |
for handler in self.handlers: | |
cls = get_class(handler) | |
for subclass in mro: | |
eval_ = getattr(cls, subclass.__name__, None) | |
if eval_ is None: | |
continue | |
res = eval_(expr, assumptions) | |
# Do not stop if value returned is None | |
# Try to check for higher classes | |
if res is None: | |
continue | |
if _res is None: | |
_res = res | |
else: | |
# only check consistency if both resolutors have concluded | |
if _res != res: | |
raise ValueError('incompatible resolutors') | |
break | |
return res | |
def assuming(*assumptions): | |
""" | |
Context manager for assumptions. | |
Examples | |
======== | |
>>> from sympy import assuming, Q, ask | |
>>> from sympy.abc import x, y | |
>>> print(ask(Q.integer(x + y))) | |
None | |
>>> with assuming(Q.integer(x), Q.integer(y)): | |
... print(ask(Q.integer(x + y))) | |
True | |
""" | |
old_global_assumptions = global_assumptions.copy() | |
global_assumptions.update(assumptions) | |
try: | |
yield | |
finally: | |
global_assumptions.clear() | |
global_assumptions.update(old_global_assumptions) | |