kind.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. """
  2. Module to efficiently partition SymPy objects.
  3. This system is introduced because class of SymPy object does not always
  4. represent the mathematical classification of the entity. For example,
  5. ``Integral(1, x)`` and ``Integral(Matrix([1,2]), x)`` are both instance
  6. of ``Integral`` class. However the former is number and the latter is
  7. matrix.
  8. One way to resolve this is defining subclass for each mathematical type,
  9. such as ``MatAdd`` for the addition between matrices. Basic algebraic
  10. operation such as addition or multiplication take this approach, but
  11. defining every class for every mathematical object is not scalable.
  12. Therefore, we define the "kind" of the object and let the expression
  13. infer the kind of itself from its arguments. Function and class can
  14. filter the arguments by their kind, and behave differently according to
  15. the type of itself.
  16. This module defines basic kinds for core objects. Other kinds such as
  17. ``ArrayKind`` or ``MatrixKind`` can be found in corresponding modules.
  18. .. notes::
  19. This approach is experimental, and can be replaced or deleted in the future.
  20. See https://github.com/sympy/sympy/pull/20549.
  21. """
  22. from collections import defaultdict
  23. from .cache import cacheit
  24. from sympy.multipledispatch.dispatcher import (Dispatcher,
  25. ambiguity_warn, ambiguity_register_error_ignore_dup,
  26. str_signature, RaiseNotImplementedError)
  27. class KindMeta(type):
  28. """
  29. Metaclass for ``Kind``.
  30. Assigns empty ``dict`` as class attribute ``_inst`` for every class,
  31. in order to endow singleton-like behavior.
  32. """
  33. def __new__(cls, clsname, bases, dct):
  34. dct['_inst'] = {}
  35. return super().__new__(cls, clsname, bases, dct)
  36. class Kind(object, metaclass=KindMeta):
  37. """
  38. Base class for kinds.
  39. Kind of the object represents the mathematical classification that
  40. the entity falls into. It is expected that functions and classes
  41. recognize and filter the argument by its kind.
  42. Kind of every object must be carefully selected so that it shows the
  43. intention of design. Expressions may have different kind according
  44. to the kind of its arguments. For example, arguments of ``Add``
  45. must have common kind since addition is group operator, and the
  46. resulting ``Add()`` has the same kind.
  47. For the performance, each kind is as broad as possible and is not
  48. based on set theory. For example, ``NumberKind`` includes not only
  49. complex number but expression containing ``S.Infinity`` or ``S.NaN``
  50. which are not strictly number.
  51. Kind may have arguments as parameter. For example, ``MatrixKind()``
  52. may be constructed with one element which represents the kind of its
  53. elements.
  54. ``Kind`` behaves in singleton-like fashion. Same signature will
  55. return the same object.
  56. """
  57. def __new__(cls, *args):
  58. if args in cls._inst:
  59. inst = cls._inst[args]
  60. else:
  61. inst = super().__new__(cls)
  62. cls._inst[args] = inst
  63. return inst
  64. class _UndefinedKind(Kind):
  65. """
  66. Default kind for all SymPy object. If the kind is not defined for
  67. the object, or if the object cannot infer the kind from its
  68. arguments, this will be returned.
  69. Examples
  70. ========
  71. >>> from sympy import Expr
  72. >>> Expr().kind
  73. UndefinedKind
  74. """
  75. def __new__(cls):
  76. return super().__new__(cls)
  77. def __repr__(self):
  78. return "UndefinedKind"
  79. UndefinedKind = _UndefinedKind()
  80. class _NumberKind(Kind):
  81. """
  82. Kind for all numeric object.
  83. This kind represents every number, including complex numbers,
  84. infinity and ``S.NaN``. Other objects such as quaternions do not
  85. have this kind.
  86. Most ``Expr`` are initially designed to represent the number, so
  87. this will be the most common kind in SymPy core. For example
  88. ``Symbol()``, which represents a scalar, has this kind as long as it
  89. is commutative.
  90. Numbers form a field. Any operation between number-kind objects will
  91. result this kind as well.
  92. Examples
  93. ========
  94. >>> from sympy import S, oo, Symbol
  95. >>> S.One.kind
  96. NumberKind
  97. >>> (-oo).kind
  98. NumberKind
  99. >>> S.NaN.kind
  100. NumberKind
  101. Commutative symbol are treated as number.
  102. >>> x = Symbol('x')
  103. >>> x.kind
  104. NumberKind
  105. >>> Symbol('y', commutative=False).kind
  106. UndefinedKind
  107. Operation between numbers results number.
  108. >>> (x+1).kind
  109. NumberKind
  110. See Also
  111. ========
  112. sympy.core.expr.Expr.is_Number : check if the object is strictly
  113. subclass of ``Number`` class.
  114. sympy.core.expr.Expr.is_number : check if the object is number
  115. without any free symbol.
  116. """
  117. def __new__(cls):
  118. return super().__new__(cls)
  119. def __repr__(self):
  120. return "NumberKind"
  121. NumberKind = _NumberKind()
  122. class _BooleanKind(Kind):
  123. """
  124. Kind for boolean objects.
  125. SymPy's ``S.true``, ``S.false``, and built-in ``True`` and ``False``
  126. have this kind. Boolean number ``1`` and ``0`` are not relevant.
  127. Examples
  128. ========
  129. >>> from sympy import S, Q
  130. >>> S.true.kind
  131. BooleanKind
  132. >>> Q.even(3).kind
  133. BooleanKind
  134. """
  135. def __new__(cls):
  136. return super().__new__(cls)
  137. def __repr__(self):
  138. return "BooleanKind"
  139. BooleanKind = _BooleanKind()
  140. class KindDispatcher:
  141. """
  142. Dispatcher to select a kind from multiple kinds by binary dispatching.
  143. .. notes::
  144. This approach is experimental, and can be replaced or deleted in
  145. the future.
  146. Explanation
  147. ===========
  148. SymPy object's :obj:`sympy.core.kind.Kind()` vaguely represents the
  149. algebraic structure where the object belongs to. Therefore, with
  150. given operation, we can always find a dominating kind among the
  151. different kinds. This class selects the kind by recursive binary
  152. dispatching. If the result cannot be determined, ``UndefinedKind``
  153. is returned.
  154. Examples
  155. ========
  156. Multiplication between numbers return number.
  157. >>> from sympy import NumberKind, Mul
  158. >>> Mul._kind_dispatcher(NumberKind, NumberKind)
  159. NumberKind
  160. Multiplication between number and unknown-kind object returns unknown kind.
  161. >>> from sympy import UndefinedKind
  162. >>> Mul._kind_dispatcher(NumberKind, UndefinedKind)
  163. UndefinedKind
  164. Any number and order of kinds is allowed.
  165. >>> Mul._kind_dispatcher(UndefinedKind, NumberKind)
  166. UndefinedKind
  167. >>> Mul._kind_dispatcher(NumberKind, UndefinedKind, NumberKind)
  168. UndefinedKind
  169. Since matrix forms a vector space over scalar field, multiplication
  170. between matrix with numeric element and number returns matrix with
  171. numeric element.
  172. >>> from sympy.matrices import MatrixKind
  173. >>> Mul._kind_dispatcher(MatrixKind(NumberKind), NumberKind)
  174. MatrixKind(NumberKind)
  175. If a matrix with number element and another matrix with unknown-kind
  176. element are multiplied, we know that the result is matrix but the
  177. kind of its elements is unknown.
  178. >>> Mul._kind_dispatcher(MatrixKind(NumberKind), MatrixKind(UndefinedKind))
  179. MatrixKind(UndefinedKind)
  180. Parameters
  181. ==========
  182. name : str
  183. commutative : bool, optional
  184. If True, binary dispatch will be automatically registered in
  185. reversed order as well.
  186. doc : str, optional
  187. """
  188. def __init__(self, name, commutative=False, doc=None):
  189. self.name = name
  190. self.doc = doc
  191. self.commutative = commutative
  192. self._dispatcher = Dispatcher(name)
  193. def __repr__(self):
  194. return "<dispatched %s>" % self.name
  195. def register(self, *types, **kwargs):
  196. """
  197. Register the binary dispatcher for two kind classes.
  198. If *self.commutative* is ``True``, signature in reversed order is
  199. automatically registered as well.
  200. """
  201. on_ambiguity = kwargs.pop("on_ambiguity", None)
  202. if not on_ambiguity:
  203. if self.commutative:
  204. on_ambiguity = ambiguity_register_error_ignore_dup
  205. else:
  206. on_ambiguity = ambiguity_warn
  207. kwargs.update(on_ambiguity=on_ambiguity)
  208. if not len(types) == 2:
  209. raise RuntimeError(
  210. "Only binary dispatch is supported, but got %s types: <%s>." % (
  211. len(types), str_signature(types)
  212. ))
  213. def _(func):
  214. self._dispatcher.add(types, func, **kwargs)
  215. if self.commutative:
  216. self._dispatcher.add(tuple(reversed(types)), func, **kwargs)
  217. return _
  218. def __call__(self, *args, **kwargs):
  219. if self.commutative:
  220. kinds = frozenset(args)
  221. else:
  222. kinds = []
  223. prev = None
  224. for a in args:
  225. if prev is not a:
  226. kinds.append(a)
  227. prev = a
  228. return self.dispatch_kinds(kinds, **kwargs)
  229. @cacheit
  230. def dispatch_kinds(self, kinds, **kwargs):
  231. # Quick exit for the case where all kinds are same
  232. if len(kinds) == 1:
  233. result, = kinds
  234. if not isinstance(result, Kind):
  235. raise RuntimeError("%s is not a kind." % result)
  236. return result
  237. for i,kind in enumerate(kinds):
  238. if not isinstance(kind, Kind):
  239. raise RuntimeError("%s is not a kind." % kind)
  240. if i == 0:
  241. result = kind
  242. else:
  243. prev_kind = result
  244. t1, t2 = type(prev_kind), type(kind)
  245. k1, k2 = prev_kind, kind
  246. func = self._dispatcher.dispatch(t1, t2)
  247. if func is None and self.commutative:
  248. # try reversed order
  249. func = self._dispatcher.dispatch(t2, t1)
  250. k1, k2 = k2, k1
  251. if func is None:
  252. # unregistered kind relation
  253. result = UndefinedKind
  254. else:
  255. result = func(k1, k2)
  256. if not isinstance(result, Kind):
  257. raise RuntimeError(
  258. "Dispatcher for {!r} and {!r} must return a Kind, but got {!r}".format(
  259. prev_kind, kind, result
  260. ))
  261. return result
  262. @property
  263. def __doc__(self):
  264. docs = [
  265. "Kind dispatcher : %s" % self.name,
  266. "Note that support for this is experimental. See the docs for :class:`KindDispatcher` for details"
  267. ]
  268. if self.doc:
  269. docs.append(self.doc)
  270. s = "Registered kind classes\n"
  271. s += '=' * len(s)
  272. docs.append(s)
  273. amb_sigs = []
  274. typ_sigs = defaultdict(list)
  275. for sigs in self._dispatcher.ordering[::-1]:
  276. key = self._dispatcher.funcs[sigs]
  277. typ_sigs[key].append(sigs)
  278. for func, sigs in typ_sigs.items():
  279. sigs_str = ', '.join('<%s>' % str_signature(sig) for sig in sigs)
  280. if isinstance(func, RaiseNotImplementedError):
  281. amb_sigs.append(sigs_str)
  282. continue
  283. s = 'Inputs: %s\n' % sigs_str
  284. s += '-' * len(s) + '\n'
  285. if func.__doc__:
  286. s += func.__doc__.strip()
  287. else:
  288. s += func.__name__
  289. docs.append(s)
  290. if amb_sigs:
  291. s = "Ambiguous kind classes\n"
  292. s += '=' * len(s)
  293. docs.append(s)
  294. s = '\n'.join(amb_sigs)
  295. docs.append(s)
  296. return '\n\n'.join(docs)