Spaces:
Running
Running
""" | |
Module to efficiently partition SymPy objects. | |
This system is introduced because class of SymPy object does not always | |
represent the mathematical classification of the entity. For example, | |
``Integral(1, x)`` and ``Integral(Matrix([1,2]), x)`` are both instance | |
of ``Integral`` class. However the former is number and the latter is | |
matrix. | |
One way to resolve this is defining subclass for each mathematical type, | |
such as ``MatAdd`` for the addition between matrices. Basic algebraic | |
operation such as addition or multiplication take this approach, but | |
defining every class for every mathematical object is not scalable. | |
Therefore, we define the "kind" of the object and let the expression | |
infer the kind of itself from its arguments. Function and class can | |
filter the arguments by their kind, and behave differently according to | |
the type of itself. | |
This module defines basic kinds for core objects. Other kinds such as | |
``ArrayKind`` or ``MatrixKind`` can be found in corresponding modules. | |
.. notes:: | |
This approach is experimental, and can be replaced or deleted in the future. | |
See https://github.com/sympy/sympy/pull/20549. | |
""" | |
from collections import defaultdict | |
from .cache import cacheit | |
from sympy.multipledispatch.dispatcher import (Dispatcher, | |
ambiguity_warn, ambiguity_register_error_ignore_dup, | |
str_signature, RaiseNotImplementedError) | |
class KindMeta(type): | |
""" | |
Metaclass for ``Kind``. | |
Assigns empty ``dict`` as class attribute ``_inst`` for every class, | |
in order to endow singleton-like behavior. | |
""" | |
def __new__(cls, clsname, bases, dct): | |
dct['_inst'] = {} | |
return super().__new__(cls, clsname, bases, dct) | |
class Kind(object, metaclass=KindMeta): | |
""" | |
Base class for kinds. | |
Kind of the object represents the mathematical classification that | |
the entity falls into. It is expected that functions and classes | |
recognize and filter the argument by its kind. | |
Kind of every object must be carefully selected so that it shows the | |
intention of design. Expressions may have different kind according | |
to the kind of its arguments. For example, arguments of ``Add`` | |
must have common kind since addition is group operator, and the | |
resulting ``Add()`` has the same kind. | |
For the performance, each kind is as broad as possible and is not | |
based on set theory. For example, ``NumberKind`` includes not only | |
complex number but expression containing ``S.Infinity`` or ``S.NaN`` | |
which are not strictly number. | |
Kind may have arguments as parameter. For example, ``MatrixKind()`` | |
may be constructed with one element which represents the kind of its | |
elements. | |
``Kind`` behaves in singleton-like fashion. Same signature will | |
return the same object. | |
""" | |
def __new__(cls, *args): | |
if args in cls._inst: | |
inst = cls._inst[args] | |
else: | |
inst = super().__new__(cls) | |
cls._inst[args] = inst | |
return inst | |
class _UndefinedKind(Kind): | |
""" | |
Default kind for all SymPy object. If the kind is not defined for | |
the object, or if the object cannot infer the kind from its | |
arguments, this will be returned. | |
Examples | |
======== | |
>>> from sympy import Expr | |
>>> Expr().kind | |
UndefinedKind | |
""" | |
def __new__(cls): | |
return super().__new__(cls) | |
def __repr__(self): | |
return "UndefinedKind" | |
UndefinedKind = _UndefinedKind() | |
class _NumberKind(Kind): | |
""" | |
Kind for all numeric object. | |
This kind represents every number, including complex numbers, | |
infinity and ``S.NaN``. Other objects such as quaternions do not | |
have this kind. | |
Most ``Expr`` are initially designed to represent the number, so | |
this will be the most common kind in SymPy core. For example | |
``Symbol()``, which represents a scalar, has this kind as long as it | |
is commutative. | |
Numbers form a field. Any operation between number-kind objects will | |
result this kind as well. | |
Examples | |
======== | |
>>> from sympy import S, oo, Symbol | |
>>> S.One.kind | |
NumberKind | |
>>> (-oo).kind | |
NumberKind | |
>>> S.NaN.kind | |
NumberKind | |
Commutative symbol are treated as number. | |
>>> x = Symbol('x') | |
>>> x.kind | |
NumberKind | |
>>> Symbol('y', commutative=False).kind | |
UndefinedKind | |
Operation between numbers results number. | |
>>> (x+1).kind | |
NumberKind | |
See Also | |
======== | |
sympy.core.expr.Expr.is_Number : check if the object is strictly | |
subclass of ``Number`` class. | |
sympy.core.expr.Expr.is_number : check if the object is number | |
without any free symbol. | |
""" | |
def __new__(cls): | |
return super().__new__(cls) | |
def __repr__(self): | |
return "NumberKind" | |
NumberKind = _NumberKind() | |
class _BooleanKind(Kind): | |
""" | |
Kind for boolean objects. | |
SymPy's ``S.true``, ``S.false``, and built-in ``True`` and ``False`` | |
have this kind. Boolean number ``1`` and ``0`` are not relevant. | |
Examples | |
======== | |
>>> from sympy import S, Q | |
>>> S.true.kind | |
BooleanKind | |
>>> Q.even(3).kind | |
BooleanKind | |
""" | |
def __new__(cls): | |
return super().__new__(cls) | |
def __repr__(self): | |
return "BooleanKind" | |
BooleanKind = _BooleanKind() | |
class KindDispatcher: | |
""" | |
Dispatcher to select a kind from multiple kinds by binary dispatching. | |
.. notes:: | |
This approach is experimental, and can be replaced or deleted in | |
the future. | |
Explanation | |
=========== | |
SymPy object's :obj:`sympy.core.kind.Kind()` vaguely represents the | |
algebraic structure where the object belongs to. Therefore, with | |
given operation, we can always find a dominating kind among the | |
different kinds. This class selects the kind by recursive binary | |
dispatching. If the result cannot be determined, ``UndefinedKind`` | |
is returned. | |
Examples | |
======== | |
Multiplication between numbers return number. | |
>>> from sympy import NumberKind, Mul | |
>>> Mul._kind_dispatcher(NumberKind, NumberKind) | |
NumberKind | |
Multiplication between number and unknown-kind object returns unknown kind. | |
>>> from sympy import UndefinedKind | |
>>> Mul._kind_dispatcher(NumberKind, UndefinedKind) | |
UndefinedKind | |
Any number and order of kinds is allowed. | |
>>> Mul._kind_dispatcher(UndefinedKind, NumberKind) | |
UndefinedKind | |
>>> Mul._kind_dispatcher(NumberKind, UndefinedKind, NumberKind) | |
UndefinedKind | |
Since matrix forms a vector space over scalar field, multiplication | |
between matrix with numeric element and number returns matrix with | |
numeric element. | |
>>> from sympy.matrices import MatrixKind | |
>>> Mul._kind_dispatcher(MatrixKind(NumberKind), NumberKind) | |
MatrixKind(NumberKind) | |
If a matrix with number element and another matrix with unknown-kind | |
element are multiplied, we know that the result is matrix but the | |
kind of its elements is unknown. | |
>>> Mul._kind_dispatcher(MatrixKind(NumberKind), MatrixKind(UndefinedKind)) | |
MatrixKind(UndefinedKind) | |
Parameters | |
========== | |
name : str | |
commutative : bool, optional | |
If True, binary dispatch will be automatically registered in | |
reversed order as well. | |
doc : str, optional | |
""" | |
def __init__(self, name, commutative=False, doc=None): | |
self.name = name | |
self.doc = doc | |
self.commutative = commutative | |
self._dispatcher = Dispatcher(name) | |
def __repr__(self): | |
return "<dispatched %s>" % self.name | |
def register(self, *types, **kwargs): | |
""" | |
Register the binary dispatcher for two kind classes. | |
If *self.commutative* is ``True``, signature in reversed order is | |
automatically registered as well. | |
""" | |
on_ambiguity = kwargs.pop("on_ambiguity", None) | |
if not on_ambiguity: | |
if self.commutative: | |
on_ambiguity = ambiguity_register_error_ignore_dup | |
else: | |
on_ambiguity = ambiguity_warn | |
kwargs.update(on_ambiguity=on_ambiguity) | |
if not len(types) == 2: | |
raise RuntimeError( | |
"Only binary dispatch is supported, but got %s types: <%s>." % ( | |
len(types), str_signature(types) | |
)) | |
def _(func): | |
self._dispatcher.add(types, func, **kwargs) | |
if self.commutative: | |
self._dispatcher.add(tuple(reversed(types)), func, **kwargs) | |
return _ | |
def __call__(self, *args, **kwargs): | |
if self.commutative: | |
kinds = frozenset(args) | |
else: | |
kinds = [] | |
prev = None | |
for a in args: | |
if prev is not a: | |
kinds.append(a) | |
prev = a | |
return self.dispatch_kinds(kinds, **kwargs) | |
def dispatch_kinds(self, kinds, **kwargs): | |
# Quick exit for the case where all kinds are same | |
if len(kinds) == 1: | |
result, = kinds | |
if not isinstance(result, Kind): | |
raise RuntimeError("%s is not a kind." % result) | |
return result | |
for i,kind in enumerate(kinds): | |
if not isinstance(kind, Kind): | |
raise RuntimeError("%s is not a kind." % kind) | |
if i == 0: | |
result = kind | |
else: | |
prev_kind = result | |
t1, t2 = type(prev_kind), type(kind) | |
k1, k2 = prev_kind, kind | |
func = self._dispatcher.dispatch(t1, t2) | |
if func is None and self.commutative: | |
# try reversed order | |
func = self._dispatcher.dispatch(t2, t1) | |
k1, k2 = k2, k1 | |
if func is None: | |
# unregistered kind relation | |
result = UndefinedKind | |
else: | |
result = func(k1, k2) | |
if not isinstance(result, Kind): | |
raise RuntimeError( | |
"Dispatcher for {!r} and {!r} must return a Kind, but got {!r}".format( | |
prev_kind, kind, result | |
)) | |
return result | |
def __doc__(self): | |
docs = [ | |
"Kind dispatcher : %s" % self.name, | |
"Note that support for this is experimental. See the docs for :class:`KindDispatcher` for details" | |
] | |
if self.doc: | |
docs.append(self.doc) | |
s = "Registered kind classes\n" | |
s += '=' * len(s) | |
docs.append(s) | |
amb_sigs = [] | |
typ_sigs = defaultdict(list) | |
for sigs in self._dispatcher.ordering[::-1]: | |
key = self._dispatcher.funcs[sigs] | |
typ_sigs[key].append(sigs) | |
for func, sigs in typ_sigs.items(): | |
sigs_str = ', '.join('<%s>' % str_signature(sig) for sig in sigs) | |
if isinstance(func, RaiseNotImplementedError): | |
amb_sigs.append(sigs_str) | |
continue | |
s = 'Inputs: %s\n' % sigs_str | |
s += '-' * len(s) + '\n' | |
if func.__doc__: | |
s += func.__doc__.strip() | |
else: | |
s += func.__name__ | |
docs.append(s) | |
if amb_sigs: | |
s = "Ambiguous kind classes\n" | |
s += '=' * len(s) | |
docs.append(s) | |
s = '\n'.join(amb_sigs) | |
docs.append(s) | |
return '\n\n'.join(docs) | |