indexed.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797
  1. r"""Module that defines indexed objects
  2. The classes ``IndexedBase``, ``Indexed``, and ``Idx`` represent a
  3. matrix element ``M[i, j]`` as in the following diagram::
  4. 1) The Indexed class represents the entire indexed object.
  5. |
  6. ___|___
  7. ' '
  8. M[i, j]
  9. / \__\______
  10. | |
  11. | |
  12. | 2) The Idx class represents indices; each Idx can
  13. | optionally contain information about its range.
  14. |
  15. 3) IndexedBase represents the 'stem' of an indexed object, here `M`.
  16. The stem used by itself is usually taken to represent the entire
  17. array.
  18. There can be any number of indices on an Indexed object. No
  19. transformation properties are implemented in these Base objects, but
  20. implicit contraction of repeated indices is supported.
  21. Note that the support for complicated (i.e. non-atomic) integer
  22. expressions as indices is limited. (This should be improved in
  23. future releases.)
  24. Examples
  25. ========
  26. To express the above matrix element example you would write:
  27. >>> from sympy import symbols, IndexedBase, Idx
  28. >>> M = IndexedBase('M')
  29. >>> i, j = symbols('i j', cls=Idx)
  30. >>> M[i, j]
  31. M[i, j]
  32. Repeated indices in a product implies a summation, so to express a
  33. matrix-vector product in terms of Indexed objects:
  34. >>> x = IndexedBase('x')
  35. >>> M[i, j]*x[j]
  36. M[i, j]*x[j]
  37. If the indexed objects will be converted to component based arrays, e.g.
  38. with the code printers or the autowrap framework, you also need to provide
  39. (symbolic or numerical) dimensions. This can be done by passing an
  40. optional shape parameter to IndexedBase upon construction:
  41. >>> dim1, dim2 = symbols('dim1 dim2', integer=True)
  42. >>> A = IndexedBase('A', shape=(dim1, 2*dim1, dim2))
  43. >>> A.shape
  44. (dim1, 2*dim1, dim2)
  45. >>> A[i, j, 3].shape
  46. (dim1, 2*dim1, dim2)
  47. If an IndexedBase object has no shape information, it is assumed that the
  48. array is as large as the ranges of its indices:
  49. >>> n, m = symbols('n m', integer=True)
  50. >>> i = Idx('i', m)
  51. >>> j = Idx('j', n)
  52. >>> M[i, j].shape
  53. (m, n)
  54. >>> M[i, j].ranges
  55. [(0, m - 1), (0, n - 1)]
  56. The above can be compared with the following:
  57. >>> A[i, 2, j].shape
  58. (dim1, 2*dim1, dim2)
  59. >>> A[i, 2, j].ranges
  60. [(0, m - 1), None, (0, n - 1)]
  61. To analyze the structure of indexed expressions, you can use the methods
  62. get_indices() and get_contraction_structure():
  63. >>> from sympy.tensor import get_indices, get_contraction_structure
  64. >>> get_indices(A[i, j, j])
  65. ({i}, {})
  66. >>> get_contraction_structure(A[i, j, j])
  67. {(j,): {A[i, j, j]}}
  68. See the appropriate docstrings for a detailed explanation of the output.
  69. """
  70. # TODO: (some ideas for improvement)
  71. #
  72. # o test and guarantee numpy compatibility
  73. # - implement full support for broadcasting
  74. # - strided arrays
  75. #
  76. # o more functions to analyze indexed expressions
  77. # - identify standard constructs, e.g matrix-vector product in a subexpression
  78. #
  79. # o functions to generate component based arrays (numpy and sympy.Matrix)
  80. # - generate a single array directly from Indexed
  81. # - convert simple sub-expressions
  82. #
  83. # o sophisticated indexing (possibly in subclasses to preserve simplicity)
  84. # - Idx with range smaller than dimension of Indexed
  85. # - Idx with stepsize != 1
  86. # - Idx with step determined by function call
  87. from collections.abc import Iterable
  88. from sympy.core.numbers import Number
  89. from sympy.core.assumptions import StdFactKB
  90. from sympy.core import Expr, Tuple, sympify, S
  91. from sympy.core.symbol import _filter_assumptions, Symbol
  92. from sympy.core.logic import fuzzy_bool, fuzzy_not
  93. from sympy.core.sympify import _sympify
  94. from sympy.functions.special.tensor_functions import KroneckerDelta
  95. from sympy.multipledispatch import dispatch
  96. from sympy.utilities.iterables import is_sequence, NotIterable
  97. from sympy.utilities.misc import filldedent
  98. class IndexException(Exception):
  99. pass
  100. class Indexed(Expr):
  101. """Represents a mathematical object with indices.
  102. >>> from sympy import Indexed, IndexedBase, Idx, symbols
  103. >>> i, j = symbols('i j', cls=Idx)
  104. >>> Indexed('A', i, j)
  105. A[i, j]
  106. It is recommended that ``Indexed`` objects be created by indexing ``IndexedBase``:
  107. ``IndexedBase('A')[i, j]`` instead of ``Indexed(IndexedBase('A'), i, j)``.
  108. >>> A = IndexedBase('A')
  109. >>> a_ij = A[i, j] # Prefer this,
  110. >>> b_ij = Indexed(A, i, j) # over this.
  111. >>> a_ij == b_ij
  112. True
  113. """
  114. is_commutative = True
  115. is_Indexed = True
  116. is_symbol = True
  117. is_Atom = True
  118. def __new__(cls, base, *args, **kw_args):
  119. from sympy.tensor.array.ndim_array import NDimArray
  120. from sympy.matrices.matrices import MatrixBase
  121. if not args:
  122. raise IndexException("Indexed needs at least one index.")
  123. if isinstance(base, (str, Symbol)):
  124. base = IndexedBase(base)
  125. elif not hasattr(base, '__getitem__') and not isinstance(base, IndexedBase):
  126. raise TypeError(filldedent("""
  127. The base can only be replaced with a string, Symbol,
  128. IndexedBase or an object with a method for getting
  129. items (i.e. an object with a `__getitem__` method).
  130. """))
  131. args = list(map(sympify, args))
  132. if isinstance(base, (NDimArray, Iterable, Tuple, MatrixBase)) and all(i.is_number for i in args):
  133. if len(args) == 1:
  134. return base[args[0]]
  135. else:
  136. return base[args]
  137. base = _sympify(base)
  138. obj = Expr.__new__(cls, base, *args, **kw_args)
  139. try:
  140. IndexedBase._set_assumptions(obj, base.assumptions0)
  141. except AttributeError:
  142. IndexedBase._set_assumptions(obj, {})
  143. return obj
  144. def _hashable_content(self):
  145. return super()._hashable_content() + tuple(sorted(self.assumptions0.items()))
  146. @property
  147. def name(self):
  148. return str(self)
  149. @property
  150. def _diff_wrt(self):
  151. """Allow derivatives with respect to an ``Indexed`` object."""
  152. return True
  153. def _eval_derivative(self, wrt):
  154. from sympy.tensor.array.ndim_array import NDimArray
  155. if isinstance(wrt, Indexed) and wrt.base == self.base:
  156. if len(self.indices) != len(wrt.indices):
  157. msg = "Different # of indices: d({!s})/d({!s})".format(self,
  158. wrt)
  159. raise IndexException(msg)
  160. result = S.One
  161. for index1, index2 in zip(self.indices, wrt.indices):
  162. result *= KroneckerDelta(index1, index2)
  163. return result
  164. elif isinstance(self.base, NDimArray):
  165. from sympy.tensor.array import derive_by_array
  166. return Indexed(derive_by_array(self.base, wrt), *self.args[1:])
  167. else:
  168. if Tuple(self.indices).has(wrt):
  169. return S.NaN
  170. return S.Zero
  171. @property
  172. def assumptions0(self):
  173. return {k: v for k, v in self._assumptions.items() if v is not None}
  174. @property
  175. def base(self):
  176. """Returns the ``IndexedBase`` of the ``Indexed`` object.
  177. Examples
  178. ========
  179. >>> from sympy import Indexed, IndexedBase, Idx, symbols
  180. >>> i, j = symbols('i j', cls=Idx)
  181. >>> Indexed('A', i, j).base
  182. A
  183. >>> B = IndexedBase('B')
  184. >>> B == B[i, j].base
  185. True
  186. """
  187. return self.args[0]
  188. @property
  189. def indices(self):
  190. """
  191. Returns the indices of the ``Indexed`` object.
  192. Examples
  193. ========
  194. >>> from sympy import Indexed, Idx, symbols
  195. >>> i, j = symbols('i j', cls=Idx)
  196. >>> Indexed('A', i, j).indices
  197. (i, j)
  198. """
  199. return self.args[1:]
  200. @property
  201. def rank(self):
  202. """
  203. Returns the rank of the ``Indexed`` object.
  204. Examples
  205. ========
  206. >>> from sympy import Indexed, Idx, symbols
  207. >>> i, j, k, l, m = symbols('i:m', cls=Idx)
  208. >>> Indexed('A', i, j).rank
  209. 2
  210. >>> q = Indexed('A', i, j, k, l, m)
  211. >>> q.rank
  212. 5
  213. >>> q.rank == len(q.indices)
  214. True
  215. """
  216. return len(self.args) - 1
  217. @property
  218. def shape(self):
  219. """Returns a list with dimensions of each index.
  220. Dimensions is a property of the array, not of the indices. Still, if
  221. the ``IndexedBase`` does not define a shape attribute, it is assumed
  222. that the ranges of the indices correspond to the shape of the array.
  223. >>> from sympy import IndexedBase, Idx, symbols
  224. >>> n, m = symbols('n m', integer=True)
  225. >>> i = Idx('i', m)
  226. >>> j = Idx('j', m)
  227. >>> A = IndexedBase('A', shape=(n, n))
  228. >>> B = IndexedBase('B')
  229. >>> A[i, j].shape
  230. (n, n)
  231. >>> B[i, j].shape
  232. (m, m)
  233. """
  234. if self.base.shape:
  235. return self.base.shape
  236. sizes = []
  237. for i in self.indices:
  238. upper = getattr(i, 'upper', None)
  239. lower = getattr(i, 'lower', None)
  240. if None in (upper, lower):
  241. raise IndexException(filldedent("""
  242. Range is not defined for all indices in: %s""" % self))
  243. try:
  244. size = upper - lower + 1
  245. except TypeError:
  246. raise IndexException(filldedent("""
  247. Shape cannot be inferred from Idx with
  248. undefined range: %s""" % self))
  249. sizes.append(size)
  250. return Tuple(*sizes)
  251. @property
  252. def ranges(self):
  253. """Returns a list of tuples with lower and upper range of each index.
  254. If an index does not define the data members upper and lower, the
  255. corresponding slot in the list contains ``None`` instead of a tuple.
  256. Examples
  257. ========
  258. >>> from sympy import Indexed,Idx, symbols
  259. >>> Indexed('A', Idx('i', 2), Idx('j', 4), Idx('k', 8)).ranges
  260. [(0, 1), (0, 3), (0, 7)]
  261. >>> Indexed('A', Idx('i', 3), Idx('j', 3), Idx('k', 3)).ranges
  262. [(0, 2), (0, 2), (0, 2)]
  263. >>> x, y, z = symbols('x y z', integer=True)
  264. >>> Indexed('A', x, y, z).ranges
  265. [None, None, None]
  266. """
  267. ranges = []
  268. sentinel = object()
  269. for i in self.indices:
  270. upper = getattr(i, 'upper', sentinel)
  271. lower = getattr(i, 'lower', sentinel)
  272. if sentinel not in (upper, lower):
  273. ranges.append((lower, upper))
  274. else:
  275. ranges.append(None)
  276. return ranges
  277. def _sympystr(self, p):
  278. indices = list(map(p.doprint, self.indices))
  279. return "%s[%s]" % (p.doprint(self.base), ", ".join(indices))
  280. @property
  281. def free_symbols(self):
  282. base_free_symbols = self.base.free_symbols
  283. indices_free_symbols = {
  284. fs for i in self.indices for fs in i.free_symbols}
  285. if base_free_symbols:
  286. return {self} | base_free_symbols | indices_free_symbols
  287. else:
  288. return indices_free_symbols
  289. @property
  290. def expr_free_symbols(self):
  291. from sympy.utilities.exceptions import sympy_deprecation_warning
  292. sympy_deprecation_warning("""
  293. The expr_free_symbols property is deprecated. Use free_symbols to get
  294. the free symbols of an expression.
  295. """,
  296. deprecated_since_version="1.9",
  297. active_deprecations_target="deprecated-expr-free-symbols")
  298. return {self}
  299. class IndexedBase(Expr, NotIterable):
  300. """Represent the base or stem of an indexed object
  301. The IndexedBase class represent an array that contains elements. The main purpose
  302. of this class is to allow the convenient creation of objects of the Indexed
  303. class. The __getitem__ method of IndexedBase returns an instance of
  304. Indexed. Alone, without indices, the IndexedBase class can be used as a
  305. notation for e.g. matrix equations, resembling what you could do with the
  306. Symbol class. But, the IndexedBase class adds functionality that is not
  307. available for Symbol instances:
  308. - An IndexedBase object can optionally store shape information. This can
  309. be used in to check array conformance and conditions for numpy
  310. broadcasting. (TODO)
  311. - An IndexedBase object implements syntactic sugar that allows easy symbolic
  312. representation of array operations, using implicit summation of
  313. repeated indices.
  314. - The IndexedBase object symbolizes a mathematical structure equivalent
  315. to arrays, and is recognized as such for code generation and automatic
  316. compilation and wrapping.
  317. >>> from sympy.tensor import IndexedBase, Idx
  318. >>> from sympy import symbols
  319. >>> A = IndexedBase('A'); A
  320. A
  321. >>> type(A)
  322. <class 'sympy.tensor.indexed.IndexedBase'>
  323. When an IndexedBase object receives indices, it returns an array with named
  324. axes, represented by an Indexed object:
  325. >>> i, j = symbols('i j', integer=True)
  326. >>> A[i, j, 2]
  327. A[i, j, 2]
  328. >>> type(A[i, j, 2])
  329. <class 'sympy.tensor.indexed.Indexed'>
  330. The IndexedBase constructor takes an optional shape argument. If given,
  331. it overrides any shape information in the indices. (But not the index
  332. ranges!)
  333. >>> m, n, o, p = symbols('m n o p', integer=True)
  334. >>> i = Idx('i', m)
  335. >>> j = Idx('j', n)
  336. >>> A[i, j].shape
  337. (m, n)
  338. >>> B = IndexedBase('B', shape=(o, p))
  339. >>> B[i, j].shape
  340. (o, p)
  341. Assumptions can be specified with keyword arguments the same way as for Symbol:
  342. >>> A_real = IndexedBase('A', real=True)
  343. >>> A_real.is_real
  344. True
  345. >>> A != A_real
  346. True
  347. Assumptions can also be inherited if a Symbol is used to initialize the IndexedBase:
  348. >>> I = symbols('I', integer=True)
  349. >>> C_inherit = IndexedBase(I)
  350. >>> C_explicit = IndexedBase('I', integer=True)
  351. >>> C_inherit == C_explicit
  352. True
  353. """
  354. is_commutative = True
  355. is_symbol = True
  356. is_Atom = True
  357. @staticmethod
  358. def _set_assumptions(obj, assumptions):
  359. """Set assumptions on obj, making sure to apply consistent values."""
  360. tmp_asm_copy = assumptions.copy()
  361. is_commutative = fuzzy_bool(assumptions.get('commutative', True))
  362. assumptions['commutative'] = is_commutative
  363. obj._assumptions = StdFactKB(assumptions)
  364. obj._assumptions._generator = tmp_asm_copy # Issue #8873
  365. def __new__(cls, label, shape=None, *, offset=S.Zero, strides=None, **kw_args):
  366. from sympy.matrices.matrices import MatrixBase
  367. from sympy.tensor.array.ndim_array import NDimArray
  368. assumptions, kw_args = _filter_assumptions(kw_args)
  369. if isinstance(label, str):
  370. label = Symbol(label, **assumptions)
  371. elif isinstance(label, Symbol):
  372. assumptions = label._merge(assumptions)
  373. elif isinstance(label, (MatrixBase, NDimArray)):
  374. return label
  375. elif isinstance(label, Iterable):
  376. return _sympify(label)
  377. else:
  378. label = _sympify(label)
  379. if is_sequence(shape):
  380. shape = Tuple(*shape)
  381. elif shape is not None:
  382. shape = Tuple(shape)
  383. if shape is not None:
  384. obj = Expr.__new__(cls, label, shape)
  385. else:
  386. obj = Expr.__new__(cls, label)
  387. obj._shape = shape
  388. obj._offset = offset
  389. obj._strides = strides
  390. obj._name = str(label)
  391. IndexedBase._set_assumptions(obj, assumptions)
  392. return obj
  393. @property
  394. def name(self):
  395. return self._name
  396. def _hashable_content(self):
  397. return super()._hashable_content() + tuple(sorted(self.assumptions0.items()))
  398. @property
  399. def assumptions0(self):
  400. return {k: v for k, v in self._assumptions.items() if v is not None}
  401. def __getitem__(self, indices, **kw_args):
  402. if is_sequence(indices):
  403. # Special case needed because M[*my_tuple] is a syntax error.
  404. if self.shape and len(self.shape) != len(indices):
  405. raise IndexException("Rank mismatch.")
  406. return Indexed(self, *indices, **kw_args)
  407. else:
  408. if self.shape and len(self.shape) != 1:
  409. raise IndexException("Rank mismatch.")
  410. return Indexed(self, indices, **kw_args)
  411. @property
  412. def shape(self):
  413. """Returns the shape of the ``IndexedBase`` object.
  414. Examples
  415. ========
  416. >>> from sympy import IndexedBase, Idx
  417. >>> from sympy.abc import x, y
  418. >>> IndexedBase('A', shape=(x, y)).shape
  419. (x, y)
  420. Note: If the shape of the ``IndexedBase`` is specified, it will override
  421. any shape information given by the indices.
  422. >>> A = IndexedBase('A', shape=(x, y))
  423. >>> B = IndexedBase('B')
  424. >>> i = Idx('i', 2)
  425. >>> j = Idx('j', 1)
  426. >>> A[i, j].shape
  427. (x, y)
  428. >>> B[i, j].shape
  429. (2, 1)
  430. """
  431. return self._shape
  432. @property
  433. def strides(self):
  434. """Returns the strided scheme for the ``IndexedBase`` object.
  435. Normally this is a tuple denoting the number of
  436. steps to take in the respective dimension when traversing
  437. an array. For code generation purposes strides='C' and
  438. strides='F' can also be used.
  439. strides='C' would mean that code printer would unroll
  440. in row-major order and 'F' means unroll in column major
  441. order.
  442. """
  443. return self._strides
  444. @property
  445. def offset(self):
  446. """Returns the offset for the ``IndexedBase`` object.
  447. This is the value added to the resulting index when the
  448. 2D Indexed object is unrolled to a 1D form. Used in code
  449. generation.
  450. Examples
  451. ==========
  452. >>> from sympy.printing import ccode
  453. >>> from sympy.tensor import IndexedBase, Idx
  454. >>> from sympy import symbols
  455. >>> l, m, n, o = symbols('l m n o', integer=True)
  456. >>> A = IndexedBase('A', strides=(l, m, n), offset=o)
  457. >>> i, j, k = map(Idx, 'ijk')
  458. >>> ccode(A[i, j, k])
  459. 'A[l*i + m*j + n*k + o]'
  460. """
  461. return self._offset
  462. @property
  463. def label(self):
  464. """Returns the label of the ``IndexedBase`` object.
  465. Examples
  466. ========
  467. >>> from sympy import IndexedBase
  468. >>> from sympy.abc import x, y
  469. >>> IndexedBase('A', shape=(x, y)).label
  470. A
  471. """
  472. return self.args[0]
  473. def _sympystr(self, p):
  474. return p.doprint(self.label)
  475. class Idx(Expr):
  476. """Represents an integer index as an ``Integer`` or integer expression.
  477. There are a number of ways to create an ``Idx`` object. The constructor
  478. takes two arguments:
  479. ``label``
  480. An integer or a symbol that labels the index.
  481. ``range``
  482. Optionally you can specify a range as either
  483. * ``Symbol`` or integer: This is interpreted as a dimension. Lower and
  484. upper bounds are set to ``0`` and ``range - 1``, respectively.
  485. * ``tuple``: The two elements are interpreted as the lower and upper
  486. bounds of the range, respectively.
  487. Note: bounds of the range are assumed to be either integer or infinite (oo
  488. and -oo are allowed to specify an unbounded range). If ``n`` is given as a
  489. bound, then ``n.is_integer`` must not return false.
  490. For convenience, if the label is given as a string it is automatically
  491. converted to an integer symbol. (Note: this conversion is not done for
  492. range or dimension arguments.)
  493. Examples
  494. ========
  495. >>> from sympy import Idx, symbols, oo
  496. >>> n, i, L, U = symbols('n i L U', integer=True)
  497. If a string is given for the label an integer ``Symbol`` is created and the
  498. bounds are both ``None``:
  499. >>> idx = Idx('qwerty'); idx
  500. qwerty
  501. >>> idx.lower, idx.upper
  502. (None, None)
  503. Both upper and lower bounds can be specified:
  504. >>> idx = Idx(i, (L, U)); idx
  505. i
  506. >>> idx.lower, idx.upper
  507. (L, U)
  508. When only a single bound is given it is interpreted as the dimension
  509. and the lower bound defaults to 0:
  510. >>> idx = Idx(i, n); idx.lower, idx.upper
  511. (0, n - 1)
  512. >>> idx = Idx(i, 4); idx.lower, idx.upper
  513. (0, 3)
  514. >>> idx = Idx(i, oo); idx.lower, idx.upper
  515. (0, oo)
  516. """
  517. is_integer = True
  518. is_finite = True
  519. is_real = True
  520. is_symbol = True
  521. is_Atom = True
  522. _diff_wrt = True
  523. def __new__(cls, label, range=None, **kw_args):
  524. if isinstance(label, str):
  525. label = Symbol(label, integer=True)
  526. label, range = list(map(sympify, (label, range)))
  527. if label.is_Number:
  528. if not label.is_integer:
  529. raise TypeError("Index is not an integer number.")
  530. return label
  531. if not label.is_integer:
  532. raise TypeError("Idx object requires an integer label.")
  533. elif is_sequence(range):
  534. if len(range) != 2:
  535. raise ValueError(filldedent("""
  536. Idx range tuple must have length 2, but got %s""" % len(range)))
  537. for bound in range:
  538. if (bound.is_integer is False and bound is not S.Infinity
  539. and bound is not S.NegativeInfinity):
  540. raise TypeError("Idx object requires integer bounds.")
  541. args = label, Tuple(*range)
  542. elif isinstance(range, Expr):
  543. if range is not S.Infinity and fuzzy_not(range.is_integer):
  544. raise TypeError("Idx object requires an integer dimension.")
  545. args = label, Tuple(0, range - 1)
  546. elif range:
  547. raise TypeError(filldedent("""
  548. The range must be an ordered iterable or
  549. integer SymPy expression."""))
  550. else:
  551. args = label,
  552. obj = Expr.__new__(cls, *args, **kw_args)
  553. obj._assumptions["finite"] = True
  554. obj._assumptions["real"] = True
  555. return obj
  556. @property
  557. def label(self):
  558. """Returns the label (Integer or integer expression) of the Idx object.
  559. Examples
  560. ========
  561. >>> from sympy import Idx, Symbol
  562. >>> x = Symbol('x', integer=True)
  563. >>> Idx(x).label
  564. x
  565. >>> j = Symbol('j', integer=True)
  566. >>> Idx(j).label
  567. j
  568. >>> Idx(j + 1).label
  569. j + 1
  570. """
  571. return self.args[0]
  572. @property
  573. def lower(self):
  574. """Returns the lower bound of the ``Idx``.
  575. Examples
  576. ========
  577. >>> from sympy import Idx
  578. >>> Idx('j', 2).lower
  579. 0
  580. >>> Idx('j', 5).lower
  581. 0
  582. >>> Idx('j').lower is None
  583. True
  584. """
  585. try:
  586. return self.args[1][0]
  587. except IndexError:
  588. return
  589. @property
  590. def upper(self):
  591. """Returns the upper bound of the ``Idx``.
  592. Examples
  593. ========
  594. >>> from sympy import Idx
  595. >>> Idx('j', 2).upper
  596. 1
  597. >>> Idx('j', 5).upper
  598. 4
  599. >>> Idx('j').upper is None
  600. True
  601. """
  602. try:
  603. return self.args[1][1]
  604. except IndexError:
  605. return
  606. def _sympystr(self, p):
  607. return p.doprint(self.label)
  608. @property
  609. def name(self):
  610. return self.label.name if self.label.is_Symbol else str(self.label)
  611. @property
  612. def free_symbols(self):
  613. return {self}
  614. @dispatch(Idx, Idx)
  615. def _eval_is_ge(lhs, rhs): # noqa:F811
  616. other_upper = rhs if rhs.upper is None else rhs.upper
  617. other_lower = rhs if rhs.lower is None else rhs.lower
  618. if lhs.lower is not None and (lhs.lower >= other_upper) == True:
  619. return True
  620. if lhs.upper is not None and (lhs.upper < other_lower) == True:
  621. return False
  622. return None
  623. @dispatch(Idx, Number) # type:ignore
  624. def _eval_is_ge(lhs, rhs): # noqa:F811
  625. other_upper = rhs
  626. other_lower = rhs
  627. if lhs.lower is not None and (lhs.lower >= other_upper) == True:
  628. return True
  629. if lhs.upper is not None and (lhs.upper < other_lower) == True:
  630. return False
  631. return None
  632. @dispatch(Number, Idx) # type:ignore
  633. def _eval_is_ge(lhs, rhs): # noqa:F811
  634. other_upper = lhs
  635. other_lower = lhs
  636. if rhs.upper is not None and (rhs.upper <= other_lower) == True:
  637. return True
  638. if rhs.lower is not None and (rhs.lower > other_upper) == True:
  639. return False
  640. return None