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

1""" 

2Module to efficiently partition SymPy objects. 

3 

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. 

9 

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. 

14 

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. 

19 

20This module defines basic kinds for core objects. Other kinds such as 

21``ArrayKind`` or ``MatrixKind`` can be found in corresponding modules. 

22 

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""" 

27 

28from collections import defaultdict 

29 

30from .cache import cacheit 

31from sympy.multipledispatch.dispatcher import (Dispatcher, 

32 ambiguity_warn, ambiguity_register_error_ignore_dup, 

33 str_signature, RaiseNotImplementedError) 

34 

35 

36class KindMeta(type): 

37 """ 

38 Metaclass for ``Kind``. 

39 

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) 

46 

47 

48class Kind(object, metaclass=KindMeta): 

49 """ 

50 Base class for kinds. 

51 

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. 

55 

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. 

61 

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. 

66 

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. 

70 

71 ``Kind`` behaves in singleton-like fashion. Same signature will 

72 return the same object. 

73 

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 

82 

83 

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. 

89 

90 Examples 

91 ======== 

92 

93 >>> from sympy import Expr 

94 >>> Expr().kind 

95 UndefinedKind 

96 """ 

97 def __new__(cls): 

98 return super().__new__(cls) 

99 

100 def __repr__(self): 

101 return "UndefinedKind" 

102 

103UndefinedKind = _UndefinedKind() 

104 

105 

106class _NumberKind(Kind): 

107 """ 

108 Kind for all numeric object. 

109 

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. 

113 

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. 

118 

119 Numbers form a field. Any operation between number-kind objects will 

120 result this kind as well. 

121 

122 Examples 

123 ======== 

124 

125 >>> from sympy import S, oo, Symbol 

126 >>> S.One.kind 

127 NumberKind 

128 >>> (-oo).kind 

129 NumberKind 

130 >>> S.NaN.kind 

131 NumberKind 

132 

133 Commutative symbol are treated as number. 

134 

135 >>> x = Symbol('x') 

136 >>> x.kind 

137 NumberKind 

138 >>> Symbol('y', commutative=False).kind 

139 UndefinedKind 

140 

141 Operation between numbers results number. 

142 

143 >>> (x+1).kind 

144 NumberKind 

145 

146 See Also 

147 ======== 

148 

149 sympy.core.expr.Expr.is_Number : check if the object is strictly 

150 subclass of ``Number`` class. 

151 

152 sympy.core.expr.Expr.is_number : check if the object is number 

153 without any free symbol. 

154 

155 """ 

156 def __new__(cls): 

157 return super().__new__(cls) 

158 

159 def __repr__(self): 

160 return "NumberKind" 

161 

162NumberKind = _NumberKind() 

163 

164 

165class _BooleanKind(Kind): 

166 """ 

167 Kind for boolean objects. 

168 

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. 

171 

172 Examples 

173 ======== 

174 

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) 

183 

184 def __repr__(self): 

185 return "BooleanKind" 

186 

187BooleanKind = _BooleanKind() 

188 

189 

190class KindDispatcher: 

191 """ 

192 Dispatcher to select a kind from multiple kinds by binary dispatching. 

193 

194 .. notes:: 

195 This approach is experimental, and can be replaced or deleted in 

196 the future. 

197 

198 Explanation 

199 =========== 

200 

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. 

207 

208 Examples 

209 ======== 

210 

211 Multiplication between numbers return number. 

212 

213 >>> from sympy import NumberKind, Mul 

214 >>> Mul._kind_dispatcher(NumberKind, NumberKind) 

215 NumberKind 

216 

217 Multiplication between number and unknown-kind object returns unknown kind. 

218 

219 >>> from sympy import UndefinedKind 

220 >>> Mul._kind_dispatcher(NumberKind, UndefinedKind) 

221 UndefinedKind 

222 

223 Any number and order of kinds is allowed. 

224 

225 >>> Mul._kind_dispatcher(UndefinedKind, NumberKind) 

226 UndefinedKind 

227 >>> Mul._kind_dispatcher(NumberKind, UndefinedKind, NumberKind) 

228 UndefinedKind 

229 

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. 

233 

234 >>> from sympy.matrices import MatrixKind 

235 >>> Mul._kind_dispatcher(MatrixKind(NumberKind), NumberKind) 

236 MatrixKind(NumberKind) 

237 

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. 

241 

242 >>> Mul._kind_dispatcher(MatrixKind(NumberKind), MatrixKind(UndefinedKind)) 

243 MatrixKind(UndefinedKind) 

244 

245 Parameters 

246 ========== 

247 

248 name : str 

249 

250 commutative : bool, optional 

251 If True, binary dispatch will be automatically registered in 

252 reversed order as well. 

253 

254 doc : str, optional 

255 

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) 

262 

263 def __repr__(self): 

264 return "<dispatched %s>" % self.name 

265 

266 def register(self, *types, **kwargs): 

267 """ 

268 Register the binary dispatcher for two kind classes. 

269 

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) 

280 

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 )) 

286 

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 _ 

292 

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) 

304 

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 

313 

314 for i,kind in enumerate(kinds): 

315 if not isinstance(kind, Kind): 

316 raise RuntimeError("%s is not a kind." % kind) 

317 

318 if i == 0: 

319 result = kind 

320 else: 

321 prev_kind = result 

322 

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 )) 

340 

341 return result 

342 

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 ] 

349 

350 if self.doc: 

351 docs.append(self.doc) 

352 

353 s = "Registered kind classes\n" 

354 s += '=' * len(s) 

355 docs.append(s) 

356 

357 amb_sigs = [] 

358 

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) 

363 

364 for func, sigs in typ_sigs.items(): 

365 

366 sigs_str = ', '.join('<%s>' % str_signature(sig) for sig in sigs) 

367 

368 if isinstance(func, RaiseNotImplementedError): 

369 amb_sigs.append(sigs_str) 

370 continue 

371 

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) 

379 

380 if amb_sigs: 

381 s = "Ambiguous kind classes\n" 

382 s += '=' * len(s) 

383 docs.append(s) 

384 

385 s = '\n'.join(amb_sigs) 

386 docs.append(s) 

387 

388 return '\n\n'.join(docs)