Coverage for /usr/lib/python3/dist-packages/sympy/core/kind.py: 43%
120 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""
2Module to efficiently partition SymPy objects.
4This system is introduced because class of SymPy object does not always
5represent the mathematical classification of the entity. For example,
6``Integral(1, x)`` and ``Integral(Matrix([1,2]), x)`` are both instance
7of ``Integral`` class. However the former is number and the latter is
8matrix.
10One way to resolve this is defining subclass for each mathematical type,
11such as ``MatAdd`` for the addition between matrices. Basic algebraic
12operation such as addition or multiplication take this approach, but
13defining every class for every mathematical object is not scalable.
15Therefore, we define the "kind" of the object and let the expression
16infer the kind of itself from its arguments. Function and class can
17filter the arguments by their kind, and behave differently according to
18the type of itself.
20This module defines basic kinds for core objects. Other kinds such as
21``ArrayKind`` or ``MatrixKind`` can be found in corresponding modules.
23.. notes::
24 This approach is experimental, and can be replaced or deleted in the future.
25 See https://github.com/sympy/sympy/pull/20549.
26"""
28from collections import defaultdict
30from .cache import cacheit
31from sympy.multipledispatch.dispatcher import (Dispatcher,
32 ambiguity_warn, ambiguity_register_error_ignore_dup,
33 str_signature, RaiseNotImplementedError)
36class KindMeta(type):
37 """
38 Metaclass for ``Kind``.
40 Assigns empty ``dict`` as class attribute ``_inst`` for every class,
41 in order to endow singleton-like behavior.
42 """
43 def __new__(cls, clsname, bases, dct):
44 dct['_inst'] = {}
45 return super().__new__(cls, clsname, bases, dct)
48class Kind(object, metaclass=KindMeta):
49 """
50 Base class for kinds.
52 Kind of the object represents the mathematical classification that
53 the entity falls into. It is expected that functions and classes
54 recognize and filter the argument by its kind.
56 Kind of every object must be carefully selected so that it shows the
57 intention of design. Expressions may have different kind according
58 to the kind of its arguments. For example, arguments of ``Add``
59 must have common kind since addition is group operator, and the
60 resulting ``Add()`` has the same kind.
62 For the performance, each kind is as broad as possible and is not
63 based on set theory. For example, ``NumberKind`` includes not only
64 complex number but expression containing ``S.Infinity`` or ``S.NaN``
65 which are not strictly number.
67 Kind may have arguments as parameter. For example, ``MatrixKind()``
68 may be constructed with one element which represents the kind of its
69 elements.
71 ``Kind`` behaves in singleton-like fashion. Same signature will
72 return the same object.
74 """
75 def __new__(cls, *args):
76 if args in cls._inst:
77 inst = cls._inst[args]
78 else:
79 inst = super().__new__(cls)
80 cls._inst[args] = inst
81 return inst
84class _UndefinedKind(Kind):
85 """
86 Default kind for all SymPy object. If the kind is not defined for
87 the object, or if the object cannot infer the kind from its
88 arguments, this will be returned.
90 Examples
91 ========
93 >>> from sympy import Expr
94 >>> Expr().kind
95 UndefinedKind
96 """
97 def __new__(cls):
98 return super().__new__(cls)
100 def __repr__(self):
101 return "UndefinedKind"
103UndefinedKind = _UndefinedKind()
106class _NumberKind(Kind):
107 """
108 Kind for all numeric object.
110 This kind represents every number, including complex numbers,
111 infinity and ``S.NaN``. Other objects such as quaternions do not
112 have this kind.
114 Most ``Expr`` are initially designed to represent the number, so
115 this will be the most common kind in SymPy core. For example
116 ``Symbol()``, which represents a scalar, has this kind as long as it
117 is commutative.
119 Numbers form a field. Any operation between number-kind objects will
120 result this kind as well.
122 Examples
123 ========
125 >>> from sympy import S, oo, Symbol
126 >>> S.One.kind
127 NumberKind
128 >>> (-oo).kind
129 NumberKind
130 >>> S.NaN.kind
131 NumberKind
133 Commutative symbol are treated as number.
135 >>> x = Symbol('x')
136 >>> x.kind
137 NumberKind
138 >>> Symbol('y', commutative=False).kind
139 UndefinedKind
141 Operation between numbers results number.
143 >>> (x+1).kind
144 NumberKind
146 See Also
147 ========
149 sympy.core.expr.Expr.is_Number : check if the object is strictly
150 subclass of ``Number`` class.
152 sympy.core.expr.Expr.is_number : check if the object is number
153 without any free symbol.
155 """
156 def __new__(cls):
157 return super().__new__(cls)
159 def __repr__(self):
160 return "NumberKind"
162NumberKind = _NumberKind()
165class _BooleanKind(Kind):
166 """
167 Kind for boolean objects.
169 SymPy's ``S.true``, ``S.false``, and built-in ``True`` and ``False``
170 have this kind. Boolean number ``1`` and ``0`` are not relevant.
172 Examples
173 ========
175 >>> from sympy import S, Q
176 >>> S.true.kind
177 BooleanKind
178 >>> Q.even(3).kind
179 BooleanKind
180 """
181 def __new__(cls):
182 return super().__new__(cls)
184 def __repr__(self):
185 return "BooleanKind"
187BooleanKind = _BooleanKind()
190class KindDispatcher:
191 """
192 Dispatcher to select a kind from multiple kinds by binary dispatching.
194 .. notes::
195 This approach is experimental, and can be replaced or deleted in
196 the future.
198 Explanation
199 ===========
201 SymPy object's :obj:`sympy.core.kind.Kind()` vaguely represents the
202 algebraic structure where the object belongs to. Therefore, with
203 given operation, we can always find a dominating kind among the
204 different kinds. This class selects the kind by recursive binary
205 dispatching. If the result cannot be determined, ``UndefinedKind``
206 is returned.
208 Examples
209 ========
211 Multiplication between numbers return number.
213 >>> from sympy import NumberKind, Mul
214 >>> Mul._kind_dispatcher(NumberKind, NumberKind)
215 NumberKind
217 Multiplication between number and unknown-kind object returns unknown kind.
219 >>> from sympy import UndefinedKind
220 >>> Mul._kind_dispatcher(NumberKind, UndefinedKind)
221 UndefinedKind
223 Any number and order of kinds is allowed.
225 >>> Mul._kind_dispatcher(UndefinedKind, NumberKind)
226 UndefinedKind
227 >>> Mul._kind_dispatcher(NumberKind, UndefinedKind, NumberKind)
228 UndefinedKind
230 Since matrix forms a vector space over scalar field, multiplication
231 between matrix with numeric element and number returns matrix with
232 numeric element.
234 >>> from sympy.matrices import MatrixKind
235 >>> Mul._kind_dispatcher(MatrixKind(NumberKind), NumberKind)
236 MatrixKind(NumberKind)
238 If a matrix with number element and another matrix with unknown-kind
239 element are multiplied, we know that the result is matrix but the
240 kind of its elements is unknown.
242 >>> Mul._kind_dispatcher(MatrixKind(NumberKind), MatrixKind(UndefinedKind))
243 MatrixKind(UndefinedKind)
245 Parameters
246 ==========
248 name : str
250 commutative : bool, optional
251 If True, binary dispatch will be automatically registered in
252 reversed order as well.
254 doc : str, optional
256 """
257 def __init__(self, name, commutative=False, doc=None):
258 self.name = name
259 self.doc = doc
260 self.commutative = commutative
261 self._dispatcher = Dispatcher(name)
263 def __repr__(self):
264 return "<dispatched %s>" % self.name
266 def register(self, *types, **kwargs):
267 """
268 Register the binary dispatcher for two kind classes.
270 If *self.commutative* is ``True``, signature in reversed order is
271 automatically registered as well.
272 """
273 on_ambiguity = kwargs.pop("on_ambiguity", None)
274 if not on_ambiguity:
275 if self.commutative:
276 on_ambiguity = ambiguity_register_error_ignore_dup
277 else:
278 on_ambiguity = ambiguity_warn
279 kwargs.update(on_ambiguity=on_ambiguity)
281 if not len(types) == 2:
282 raise RuntimeError(
283 "Only binary dispatch is supported, but got %s types: <%s>." % (
284 len(types), str_signature(types)
285 ))
287 def _(func):
288 self._dispatcher.add(types, func, **kwargs)
289 if self.commutative:
290 self._dispatcher.add(tuple(reversed(types)), func, **kwargs)
291 return _
293 def __call__(self, *args, **kwargs):
294 if self.commutative:
295 kinds = frozenset(args)
296 else:
297 kinds = []
298 prev = None
299 for a in args:
300 if prev is not a:
301 kinds.append(a)
302 prev = a
303 return self.dispatch_kinds(kinds, **kwargs)
305 @cacheit
306 def dispatch_kinds(self, kinds, **kwargs):
307 # Quick exit for the case where all kinds are same
308 if len(kinds) == 1:
309 result, = kinds
310 if not isinstance(result, Kind):
311 raise RuntimeError("%s is not a kind." % result)
312 return result
314 for i,kind in enumerate(kinds):
315 if not isinstance(kind, Kind):
316 raise RuntimeError("%s is not a kind." % kind)
318 if i == 0:
319 result = kind
320 else:
321 prev_kind = result
323 t1, t2 = type(prev_kind), type(kind)
324 k1, k2 = prev_kind, kind
325 func = self._dispatcher.dispatch(t1, t2)
326 if func is None and self.commutative:
327 # try reversed order
328 func = self._dispatcher.dispatch(t2, t1)
329 k1, k2 = k2, k1
330 if func is None:
331 # unregistered kind relation
332 result = UndefinedKind
333 else:
334 result = func(k1, k2)
335 if not isinstance(result, Kind):
336 raise RuntimeError(
337 "Dispatcher for {!r} and {!r} must return a Kind, but got {!r}".format(
338 prev_kind, kind, result
339 ))
341 return result
343 @property
344 def __doc__(self):
345 docs = [
346 "Kind dispatcher : %s" % self.name,
347 "Note that support for this is experimental. See the docs for :class:`KindDispatcher` for details"
348 ]
350 if self.doc:
351 docs.append(self.doc)
353 s = "Registered kind classes\n"
354 s += '=' * len(s)
355 docs.append(s)
357 amb_sigs = []
359 typ_sigs = defaultdict(list)
360 for sigs in self._dispatcher.ordering[::-1]:
361 key = self._dispatcher.funcs[sigs]
362 typ_sigs[key].append(sigs)
364 for func, sigs in typ_sigs.items():
366 sigs_str = ', '.join('<%s>' % str_signature(sig) for sig in sigs)
368 if isinstance(func, RaiseNotImplementedError):
369 amb_sigs.append(sigs_str)
370 continue
372 s = 'Inputs: %s\n' % sigs_str
373 s += '-' * len(s) + '\n'
374 if func.__doc__:
375 s += func.__doc__.strip()
376 else:
377 s += func.__name__
378 docs.append(s)
380 if amb_sigs:
381 s = "Ambiguous kind classes\n"
382 s += '=' * len(s)
383 docs.append(s)
385 s = '\n'.join(amb_sigs)
386 docs.append(s)
388 return '\n\n'.join(docs)