123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 |
- """
- 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)
- @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)
|