Coverage for /usr/lib/python3/dist-packages/sympy/core/containers.py: 59%

170 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

1"""Module for SymPy containers 

2 

3 (SymPy objects that store other SymPy objects) 

4 

5 The containers implemented in this module are subclassed to Basic. 

6 They are supposed to work seamlessly within the SymPy framework. 

7""" 

8 

9from collections import OrderedDict 

10from collections.abc import MutableSet 

11from typing import Any, Callable 

12 

13from .basic import Basic 

14from .sorting import default_sort_key, ordered 

15from .sympify import _sympify, sympify, _sympy_converter, SympifyError 

16from sympy.core.kind import Kind 

17from sympy.utilities.iterables import iterable 

18from sympy.utilities.misc import as_int 

19 

20 

21class Tuple(Basic): 

22 """ 

23 Wrapper around the builtin tuple object. 

24 

25 Explanation 

26 =========== 

27 

28 The Tuple is a subclass of Basic, so that it works well in the 

29 SymPy framework. The wrapped tuple is available as self.args, but 

30 you can also access elements or slices with [:] syntax. 

31 

32 Parameters 

33 ========== 

34 

35 sympify : bool 

36 If ``False``, ``sympify`` is not called on ``args``. This 

37 can be used for speedups for very large tuples where the 

38 elements are known to already be SymPy objects. 

39 

40 Examples 

41 ======== 

42 

43 >>> from sympy import Tuple, symbols 

44 >>> a, b, c, d = symbols('a b c d') 

45 >>> Tuple(a, b, c)[1:] 

46 (b, c) 

47 >>> Tuple(a, b, c).subs(a, d) 

48 (d, b, c) 

49 

50 """ 

51 

52 def __new__(cls, *args, **kwargs): 

53 if kwargs.get('sympify', True): 

54 args = (sympify(arg) for arg in args) 

55 obj = Basic.__new__(cls, *args) 

56 return obj 

57 

58 def __getitem__(self, i): 

59 if isinstance(i, slice): 

60 indices = i.indices(len(self)) 

61 return Tuple(*(self.args[j] for j in range(*indices))) 

62 return self.args[i] 

63 

64 def __len__(self): 

65 return len(self.args) 

66 

67 def __contains__(self, item): 

68 return item in self.args 

69 

70 def __iter__(self): 

71 return iter(self.args) 

72 

73 def __add__(self, other): 

74 if isinstance(other, Tuple): 

75 return Tuple(*(self.args + other.args)) 

76 elif isinstance(other, tuple): 

77 return Tuple(*(self.args + other)) 

78 else: 

79 return NotImplemented 

80 

81 def __radd__(self, other): 

82 if isinstance(other, Tuple): 

83 return Tuple(*(other.args + self.args)) 

84 elif isinstance(other, tuple): 

85 return Tuple(*(other + self.args)) 

86 else: 

87 return NotImplemented 

88 

89 def __mul__(self, other): 

90 try: 

91 n = as_int(other) 

92 except ValueError: 

93 raise TypeError("Can't multiply sequence by non-integer of type '%s'" % type(other)) 

94 return self.func(*(self.args*n)) 

95 

96 __rmul__ = __mul__ 

97 

98 def __eq__(self, other): 

99 if isinstance(other, Basic): 

100 return super().__eq__(other) 

101 return self.args == other 

102 

103 def __ne__(self, other): 

104 if isinstance(other, Basic): 

105 return super().__ne__(other) 

106 return self.args != other 

107 

108 def __hash__(self): 

109 return hash(self.args) 

110 

111 def _to_mpmath(self, prec): 

112 return tuple(a._to_mpmath(prec) for a in self.args) 

113 

114 def __lt__(self, other): 

115 return _sympify(self.args < other.args) 

116 

117 def __le__(self, other): 

118 return _sympify(self.args <= other.args) 

119 

120 # XXX: Basic defines count() as something different, so we can't 

121 # redefine it here. Originally this lead to cse() test failure. 

122 def tuple_count(self, value) -> int: 

123 """Return number of occurrences of value.""" 

124 return self.args.count(value) 

125 

126 def index(self, value, start=None, stop=None): 

127 """Searches and returns the first index of the value.""" 

128 # XXX: One would expect: 

129 # 

130 # return self.args.index(value, start, stop) 

131 # 

132 # here. Any trouble with that? Yes: 

133 # 

134 # >>> (1,).index(1, None, None) 

135 # Traceback (most recent call last): 

136 # File "<stdin>", line 1, in <module> 

137 # TypeError: slice indices must be integers or None or have an __index__ method 

138 # 

139 # See: http://bugs.python.org/issue13340 

140 

141 if start is None and stop is None: 

142 return self.args.index(value) 

143 elif stop is None: 

144 return self.args.index(value, start) 

145 else: 

146 return self.args.index(value, start, stop) 

147 

148 @property 

149 def kind(self): 

150 """ 

151 The kind of a Tuple instance. 

152 

153 The kind of a Tuple is always of :class:`TupleKind` but 

154 parametrised by the number of elements and the kind of each element. 

155 

156 Examples 

157 ======== 

158 

159 >>> from sympy import Tuple, Matrix 

160 >>> Tuple(1, 2).kind 

161 TupleKind(NumberKind, NumberKind) 

162 >>> Tuple(Matrix([1, 2]), 1).kind 

163 TupleKind(MatrixKind(NumberKind), NumberKind) 

164 >>> Tuple(1, 2).kind.element_kind 

165 (NumberKind, NumberKind) 

166 

167 See Also 

168 ======== 

169 

170 sympy.matrices.common.MatrixKind 

171 sympy.core.kind.NumberKind 

172 """ 

173 return TupleKind(*(i.kind for i in self.args)) 

174 

175_sympy_converter[tuple] = lambda tup: Tuple(*tup) 

176 

177 

178 

179 

180 

181def tuple_wrapper(method): 

182 """ 

183 Decorator that converts any tuple in the function arguments into a Tuple. 

184 

185 Explanation 

186 =========== 

187 

188 The motivation for this is to provide simple user interfaces. The user can 

189 call a function with regular tuples in the argument, and the wrapper will 

190 convert them to Tuples before handing them to the function. 

191 

192 Explanation 

193 =========== 

194 

195 >>> from sympy.core.containers import tuple_wrapper 

196 >>> def f(*args): 

197 ... return args 

198 >>> g = tuple_wrapper(f) 

199 

200 The decorated function g sees only the Tuple argument: 

201 

202 >>> g(0, (1, 2), 3) 

203 (0, (1, 2), 3) 

204 

205 """ 

206 def wrap_tuples(*args, **kw_args): 

207 newargs = [] 

208 for arg in args: 

209 if isinstance(arg, tuple): 

210 newargs.append(Tuple(*arg)) 

211 else: 

212 newargs.append(arg) 

213 return method(*newargs, **kw_args) 

214 return wrap_tuples 

215 

216 

217class Dict(Basic): 

218 """ 

219 Wrapper around the builtin dict object. 

220 

221 Explanation 

222 =========== 

223 

224 The Dict is a subclass of Basic, so that it works well in the 

225 SymPy framework. Because it is immutable, it may be included 

226 in sets, but its values must all be given at instantiation and 

227 cannot be changed afterwards. Otherwise it behaves identically 

228 to the Python dict. 

229 

230 Examples 

231 ======== 

232 

233 >>> from sympy import Dict, Symbol 

234 

235 >>> D = Dict({1: 'one', 2: 'two'}) 

236 >>> for key in D: 

237 ... if key == 1: 

238 ... print('%s %s' % (key, D[key])) 

239 1 one 

240 

241 The args are sympified so the 1 and 2 are Integers and the values 

242 are Symbols. Queries automatically sympify args so the following work: 

243 

244 >>> 1 in D 

245 True 

246 >>> D.has(Symbol('one')) # searches keys and values 

247 True 

248 >>> 'one' in D # not in the keys 

249 False 

250 >>> D[1] 

251 one 

252 

253 """ 

254 

255 def __new__(cls, *args): 

256 if len(args) == 1 and isinstance(args[0], (dict, Dict)): 

257 items = [Tuple(k, v) for k, v in args[0].items()] 

258 elif iterable(args) and all(len(arg) == 2 for arg in args): 

259 items = [Tuple(k, v) for k, v in args] 

260 else: 

261 raise TypeError('Pass Dict args as Dict((k1, v1), ...) or Dict({k1: v1, ...})') 

262 elements = frozenset(items) 

263 obj = Basic.__new__(cls, *ordered(items)) 

264 obj.elements = elements 

265 obj._dict = dict(items) # In case Tuple decides it wants to sympify 

266 return obj 

267 

268 def __getitem__(self, key): 

269 """x.__getitem__(y) <==> x[y]""" 

270 try: 

271 key = _sympify(key) 

272 except SympifyError: 

273 raise KeyError(key) 

274 

275 return self._dict[key] 

276 

277 def __setitem__(self, key, value): 

278 raise NotImplementedError("SymPy Dicts are Immutable") 

279 

280 def items(self): 

281 '''Returns a set-like object providing a view on dict's items. 

282 ''' 

283 return self._dict.items() 

284 

285 def keys(self): 

286 '''Returns the list of the dict's keys.''' 

287 return self._dict.keys() 

288 

289 def values(self): 

290 '''Returns the list of the dict's values.''' 

291 return self._dict.values() 

292 

293 def __iter__(self): 

294 '''x.__iter__() <==> iter(x)''' 

295 return iter(self._dict) 

296 

297 def __len__(self): 

298 '''x.__len__() <==> len(x)''' 

299 return self._dict.__len__() 

300 

301 def get(self, key, default=None): 

302 '''Returns the value for key if the key is in the dictionary.''' 

303 try: 

304 key = _sympify(key) 

305 except SympifyError: 

306 return default 

307 return self._dict.get(key, default) 

308 

309 def __contains__(self, key): 

310 '''D.__contains__(k) -> True if D has a key k, else False''' 

311 try: 

312 key = _sympify(key) 

313 except SympifyError: 

314 return False 

315 return key in self._dict 

316 

317 def __lt__(self, other): 

318 return _sympify(self.args < other.args) 

319 

320 @property 

321 def _sorted_args(self): 

322 return tuple(sorted(self.args, key=default_sort_key)) 

323 

324 def __eq__(self, other): 

325 if isinstance(other, dict): 

326 return self == Dict(other) 

327 return super().__eq__(other) 

328 

329 __hash__ : Callable[[Basic], Any] = Basic.__hash__ 

330 

331# this handles dict, defaultdict, OrderedDict 

332_sympy_converter[dict] = lambda d: Dict(*d.items()) 

333 

334class OrderedSet(MutableSet): 

335 def __init__(self, iterable=None): 

336 if iterable: 

337 self.map = OrderedDict((item, None) for item in iterable) 

338 else: 

339 self.map = OrderedDict() 

340 

341 def __len__(self): 

342 return len(self.map) 

343 

344 def __contains__(self, key): 

345 return key in self.map 

346 

347 def add(self, key): 

348 self.map[key] = None 

349 

350 def discard(self, key): 

351 self.map.pop(key) 

352 

353 def pop(self, last=True): 

354 return self.map.popitem(last=last)[0] 

355 

356 def __iter__(self): 

357 yield from self.map.keys() 

358 

359 def __repr__(self): 

360 if not self.map: 

361 return '%s()' % (self.__class__.__name__,) 

362 return '%s(%r)' % (self.__class__.__name__, list(self.map.keys())) 

363 

364 def intersection(self, other): 

365 return self.__class__([val for val in self if val in other]) 

366 

367 def difference(self, other): 

368 return self.__class__([val for val in self if val not in other]) 

369 

370 def update(self, iterable): 

371 for val in iterable: 

372 self.add(val) 

373 

374class TupleKind(Kind): 

375 """ 

376 TupleKind is a subclass of Kind, which is used to define Kind of ``Tuple``. 

377 

378 Parameters of TupleKind will be kinds of all the arguments in Tuples, for 

379 example 

380 

381 Parameters 

382 ========== 

383 

384 args : tuple(element_kind) 

385 element_kind is kind of element. 

386 args is tuple of kinds of element 

387 

388 Examples 

389 ======== 

390 

391 >>> from sympy import Tuple 

392 >>> Tuple(1, 2).kind 

393 TupleKind(NumberKind, NumberKind) 

394 >>> Tuple(1, 2).kind.element_kind 

395 (NumberKind, NumberKind) 

396 

397 See Also 

398 ======== 

399 

400 sympy.core.kind.NumberKind 

401 MatrixKind 

402 sympy.sets.sets.SetKind 

403 """ 

404 def __new__(cls, *args): 

405 obj = super().__new__(cls, *args) 

406 obj.element_kind = args 

407 return obj 

408 

409 def __repr__(self): 

410 return "TupleKind{}".format(self.element_kind)