Spaces:
Running
Running
File size: 11,540 Bytes
6a86ad5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 |
"""
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)
|