""" 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 "" % 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) @cacheit 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 @property 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)