indexed.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793
  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_Indexed = True
  115. is_symbol = True
  116. is_Atom = True
  117. def __new__(cls, base, *args, **kw_args):
  118. from sympy.tensor.array.ndim_array import NDimArray
  119. from sympy.matrices.matrixbase import MatrixBase
  120. if not args:
  121. raise IndexException("Indexed needs at least one index.")
  122. if isinstance(base, (str, Symbol)):
  123. base = IndexedBase(base)
  124. elif not hasattr(base, '__getitem__') and not isinstance(base, IndexedBase):
  125. raise TypeError(filldedent("""
  126. The base can only be replaced with a string, Symbol,
  127. IndexedBase or an object with a method for getting
  128. items (i.e. an object with a `__getitem__` method).
  129. """))
  130. args = list(map(sympify, args))
  131. if isinstance(base, (NDimArray, Iterable, Tuple, MatrixBase)) and all(i.is_number for i in args):
  132. if len(args) == 1:
  133. return base[args[0]]
  134. else:
  135. return base[args]
  136. base = _sympify(base)
  137. obj = Expr.__new__(cls, base, *args, **kw_args)
  138. IndexedBase._set_assumptions(obj, base.assumptions0)
  139. return obj
  140. def _hashable_content(self):
  141. return super()._hashable_content() + tuple(sorted(self.assumptions0.items()))
  142. @property
  143. def name(self):
  144. return str(self)
  145. @property
  146. def _diff_wrt(self):
  147. """Allow derivatives with respect to an ``Indexed`` object."""
  148. return True
  149. def _eval_derivative(self, wrt):
  150. from sympy.tensor.array.ndim_array import NDimArray
  151. if isinstance(wrt, Indexed) and wrt.base == self.base:
  152. if len(self.indices) != len(wrt.indices):
  153. msg = "Different # of indices: d({!s})/d({!s})".format(self,
  154. wrt)
  155. raise IndexException(msg)
  156. result = S.One
  157. for index1, index2 in zip(self.indices, wrt.indices):
  158. result *= KroneckerDelta(index1, index2)
  159. return result
  160. elif isinstance(self.base, NDimArray):
  161. from sympy.tensor.array import derive_by_array
  162. return Indexed(derive_by_array(self.base, wrt), *self.args[1:])
  163. else:
  164. if Tuple(self.indices).has(wrt):
  165. return S.NaN
  166. return S.Zero
  167. @property
  168. def assumptions0(self):
  169. return {k: v for k, v in self._assumptions.items() if v is not None}
  170. @property
  171. def base(self):
  172. """Returns the ``IndexedBase`` of the ``Indexed`` object.
  173. Examples
  174. ========
  175. >>> from sympy import Indexed, IndexedBase, Idx, symbols
  176. >>> i, j = symbols('i j', cls=Idx)
  177. >>> Indexed('A', i, j).base
  178. A
  179. >>> B = IndexedBase('B')
  180. >>> B == B[i, j].base
  181. True
  182. """
  183. return self.args[0]
  184. @property
  185. def indices(self):
  186. """
  187. Returns the indices of the ``Indexed`` object.
  188. Examples
  189. ========
  190. >>> from sympy import Indexed, Idx, symbols
  191. >>> i, j = symbols('i j', cls=Idx)
  192. >>> Indexed('A', i, j).indices
  193. (i, j)
  194. """
  195. return self.args[1:]
  196. @property
  197. def rank(self):
  198. """
  199. Returns the rank of the ``Indexed`` object.
  200. Examples
  201. ========
  202. >>> from sympy import Indexed, Idx, symbols
  203. >>> i, j, k, l, m = symbols('i:m', cls=Idx)
  204. >>> Indexed('A', i, j).rank
  205. 2
  206. >>> q = Indexed('A', i, j, k, l, m)
  207. >>> q.rank
  208. 5
  209. >>> q.rank == len(q.indices)
  210. True
  211. """
  212. return len(self.args) - 1
  213. @property
  214. def shape(self):
  215. """Returns a list with dimensions of each index.
  216. Dimensions is a property of the array, not of the indices. Still, if
  217. the ``IndexedBase`` does not define a shape attribute, it is assumed
  218. that the ranges of the indices correspond to the shape of the array.
  219. >>> from sympy import IndexedBase, Idx, symbols
  220. >>> n, m = symbols('n m', integer=True)
  221. >>> i = Idx('i', m)
  222. >>> j = Idx('j', m)
  223. >>> A = IndexedBase('A', shape=(n, n))
  224. >>> B = IndexedBase('B')
  225. >>> A[i, j].shape
  226. (n, n)
  227. >>> B[i, j].shape
  228. (m, m)
  229. """
  230. if self.base.shape:
  231. return self.base.shape
  232. sizes = []
  233. for i in self.indices:
  234. upper = getattr(i, 'upper', None)
  235. lower = getattr(i, 'lower', None)
  236. if None in (upper, lower):
  237. raise IndexException(filldedent("""
  238. Range is not defined for all indices in: %s""" % self))
  239. try:
  240. size = upper - lower + 1
  241. except TypeError:
  242. raise IndexException(filldedent("""
  243. Shape cannot be inferred from Idx with
  244. undefined range: %s""" % self))
  245. sizes.append(size)
  246. return Tuple(*sizes)
  247. @property
  248. def ranges(self):
  249. """Returns a list of tuples with lower and upper range of each index.
  250. If an index does not define the data members upper and lower, the
  251. corresponding slot in the list contains ``None`` instead of a tuple.
  252. Examples
  253. ========
  254. >>> from sympy import Indexed,Idx, symbols
  255. >>> Indexed('A', Idx('i', 2), Idx('j', 4), Idx('k', 8)).ranges
  256. [(0, 1), (0, 3), (0, 7)]
  257. >>> Indexed('A', Idx('i', 3), Idx('j', 3), Idx('k', 3)).ranges
  258. [(0, 2), (0, 2), (0, 2)]
  259. >>> x, y, z = symbols('x y z', integer=True)
  260. >>> Indexed('A', x, y, z).ranges
  261. [None, None, None]
  262. """
  263. ranges = []
  264. sentinel = object()
  265. for i in self.indices:
  266. upper = getattr(i, 'upper', sentinel)
  267. lower = getattr(i, 'lower', sentinel)
  268. if sentinel not in (upper, lower):
  269. ranges.append((lower, upper))
  270. else:
  271. ranges.append(None)
  272. return ranges
  273. def _sympystr(self, p):
  274. indices = list(map(p.doprint, self.indices))
  275. return "%s[%s]" % (p.doprint(self.base), ", ".join(indices))
  276. @property
  277. def free_symbols(self):
  278. base_free_symbols = self.base.free_symbols
  279. indices_free_symbols = {
  280. fs for i in self.indices for fs in i.free_symbols}
  281. if base_free_symbols:
  282. return {self} | base_free_symbols | indices_free_symbols
  283. else:
  284. return indices_free_symbols
  285. @property
  286. def expr_free_symbols(self):
  287. from sympy.utilities.exceptions import sympy_deprecation_warning
  288. sympy_deprecation_warning("""
  289. The expr_free_symbols property is deprecated. Use free_symbols to get
  290. the free symbols of an expression.
  291. """,
  292. deprecated_since_version="1.9",
  293. active_deprecations_target="deprecated-expr-free-symbols")
  294. return {self}
  295. class IndexedBase(Expr, NotIterable):
  296. """Represent the base or stem of an indexed object
  297. The IndexedBase class represent an array that contains elements. The main purpose
  298. of this class is to allow the convenient creation of objects of the Indexed
  299. class. The __getitem__ method of IndexedBase returns an instance of
  300. Indexed. Alone, without indices, the IndexedBase class can be used as a
  301. notation for e.g. matrix equations, resembling what you could do with the
  302. Symbol class. But, the IndexedBase class adds functionality that is not
  303. available for Symbol instances:
  304. - An IndexedBase object can optionally store shape information. This can
  305. be used in to check array conformance and conditions for numpy
  306. broadcasting. (TODO)
  307. - An IndexedBase object implements syntactic sugar that allows easy symbolic
  308. representation of array operations, using implicit summation of
  309. repeated indices.
  310. - The IndexedBase object symbolizes a mathematical structure equivalent
  311. to arrays, and is recognized as such for code generation and automatic
  312. compilation and wrapping.
  313. >>> from sympy.tensor import IndexedBase, Idx
  314. >>> from sympy import symbols
  315. >>> A = IndexedBase('A'); A
  316. A
  317. >>> type(A)
  318. <class 'sympy.tensor.indexed.IndexedBase'>
  319. When an IndexedBase object receives indices, it returns an array with named
  320. axes, represented by an Indexed object:
  321. >>> i, j = symbols('i j', integer=True)
  322. >>> A[i, j, 2]
  323. A[i, j, 2]
  324. >>> type(A[i, j, 2])
  325. <class 'sympy.tensor.indexed.Indexed'>
  326. The IndexedBase constructor takes an optional shape argument. If given,
  327. it overrides any shape information in the indices. (But not the index
  328. ranges!)
  329. >>> m, n, o, p = symbols('m n o p', integer=True)
  330. >>> i = Idx('i', m)
  331. >>> j = Idx('j', n)
  332. >>> A[i, j].shape
  333. (m, n)
  334. >>> B = IndexedBase('B', shape=(o, p))
  335. >>> B[i, j].shape
  336. (o, p)
  337. Assumptions can be specified with keyword arguments the same way as for Symbol:
  338. >>> A_real = IndexedBase('A', real=True)
  339. >>> A_real.is_real
  340. True
  341. >>> A != A_real
  342. True
  343. Assumptions can also be inherited if a Symbol is used to initialize the IndexedBase:
  344. >>> I = symbols('I', integer=True)
  345. >>> C_inherit = IndexedBase(I)
  346. >>> C_explicit = IndexedBase('I', integer=True)
  347. >>> C_inherit == C_explicit
  348. True
  349. """
  350. is_symbol = True
  351. is_Atom = True
  352. @staticmethod
  353. def _set_assumptions(obj, assumptions):
  354. """Set assumptions on obj, making sure to apply consistent values."""
  355. tmp_asm_copy = assumptions.copy()
  356. is_commutative = fuzzy_bool(assumptions.get('commutative', True))
  357. assumptions['commutative'] = is_commutative
  358. obj._assumptions = StdFactKB(assumptions)
  359. obj._assumptions._generator = tmp_asm_copy # Issue #8873
  360. def __new__(cls, label, shape=None, *, offset=S.Zero, strides=None, **kw_args):
  361. from sympy.matrices.matrixbase import MatrixBase
  362. from sympy.tensor.array.ndim_array import NDimArray
  363. assumptions, kw_args = _filter_assumptions(kw_args)
  364. if isinstance(label, str):
  365. label = Symbol(label, **assumptions)
  366. elif isinstance(label, Symbol):
  367. assumptions = label._merge(assumptions)
  368. elif isinstance(label, (MatrixBase, NDimArray)):
  369. return label
  370. elif isinstance(label, Iterable):
  371. return _sympify(label)
  372. else:
  373. label = _sympify(label)
  374. if is_sequence(shape):
  375. shape = Tuple(*shape)
  376. elif shape is not None:
  377. shape = Tuple(shape)
  378. if shape is not None:
  379. obj = Expr.__new__(cls, label, shape)
  380. else:
  381. obj = Expr.__new__(cls, label)
  382. obj._shape = shape
  383. obj._offset = offset
  384. obj._strides = strides
  385. obj._name = str(label)
  386. IndexedBase._set_assumptions(obj, assumptions)
  387. return obj
  388. @property
  389. def name(self):
  390. return self._name
  391. def _hashable_content(self):
  392. return super()._hashable_content() + tuple(sorted(self.assumptions0.items()))
  393. @property
  394. def assumptions0(self):
  395. return {k: v for k, v in self._assumptions.items() if v is not None}
  396. def __getitem__(self, indices, **kw_args):
  397. if is_sequence(indices):
  398. # Special case needed because M[*my_tuple] is a syntax error.
  399. if self.shape and len(self.shape) != len(indices):
  400. raise IndexException("Rank mismatch.")
  401. return Indexed(self, *indices, **kw_args)
  402. else:
  403. if self.shape and len(self.shape) != 1:
  404. raise IndexException("Rank mismatch.")
  405. return Indexed(self, indices, **kw_args)
  406. @property
  407. def shape(self):
  408. """Returns the shape of the ``IndexedBase`` object.
  409. Examples
  410. ========
  411. >>> from sympy import IndexedBase, Idx
  412. >>> from sympy.abc import x, y
  413. >>> IndexedBase('A', shape=(x, y)).shape
  414. (x, y)
  415. Note: If the shape of the ``IndexedBase`` is specified, it will override
  416. any shape information given by the indices.
  417. >>> A = IndexedBase('A', shape=(x, y))
  418. >>> B = IndexedBase('B')
  419. >>> i = Idx('i', 2)
  420. >>> j = Idx('j', 1)
  421. >>> A[i, j].shape
  422. (x, y)
  423. >>> B[i, j].shape
  424. (2, 1)
  425. """
  426. return self._shape
  427. @property
  428. def strides(self):
  429. """Returns the strided scheme for the ``IndexedBase`` object.
  430. Normally this is a tuple denoting the number of
  431. steps to take in the respective dimension when traversing
  432. an array. For code generation purposes strides='C' and
  433. strides='F' can also be used.
  434. strides='C' would mean that code printer would unroll
  435. in row-major order and 'F' means unroll in column major
  436. order.
  437. """
  438. return self._strides
  439. @property
  440. def offset(self):
  441. """Returns the offset for the ``IndexedBase`` object.
  442. This is the value added to the resulting index when the
  443. 2D Indexed object is unrolled to a 1D form. Used in code
  444. generation.
  445. Examples
  446. ==========
  447. >>> from sympy.printing import ccode
  448. >>> from sympy.tensor import IndexedBase, Idx
  449. >>> from sympy import symbols
  450. >>> l, m, n, o = symbols('l m n o', integer=True)
  451. >>> A = IndexedBase('A', strides=(l, m, n), offset=o)
  452. >>> i, j, k = map(Idx, 'ijk')
  453. >>> ccode(A[i, j, k])
  454. 'A[l*i + m*j + n*k + o]'
  455. """
  456. return self._offset
  457. @property
  458. def label(self):
  459. """Returns the label of the ``IndexedBase`` object.
  460. Examples
  461. ========
  462. >>> from sympy import IndexedBase
  463. >>> from sympy.abc import x, y
  464. >>> IndexedBase('A', shape=(x, y)).label
  465. A
  466. """
  467. return self.args[0]
  468. def _sympystr(self, p):
  469. return p.doprint(self.label)
  470. class Idx(Expr):
  471. """Represents an integer index as an ``Integer`` or integer expression.
  472. There are a number of ways to create an ``Idx`` object. The constructor
  473. takes two arguments:
  474. ``label``
  475. An integer or a symbol that labels the index.
  476. ``range``
  477. Optionally you can specify a range as either
  478. * ``Symbol`` or integer: This is interpreted as a dimension. Lower and
  479. upper bounds are set to ``0`` and ``range - 1``, respectively.
  480. * ``tuple``: The two elements are interpreted as the lower and upper
  481. bounds of the range, respectively.
  482. Note: bounds of the range are assumed to be either integer or infinite (oo
  483. and -oo are allowed to specify an unbounded range). If ``n`` is given as a
  484. bound, then ``n.is_integer`` must not return false.
  485. For convenience, if the label is given as a string it is automatically
  486. converted to an integer symbol. (Note: this conversion is not done for
  487. range or dimension arguments.)
  488. Examples
  489. ========
  490. >>> from sympy import Idx, symbols, oo
  491. >>> n, i, L, U = symbols('n i L U', integer=True)
  492. If a string is given for the label an integer ``Symbol`` is created and the
  493. bounds are both ``None``:
  494. >>> idx = Idx('qwerty'); idx
  495. qwerty
  496. >>> idx.lower, idx.upper
  497. (None, None)
  498. Both upper and lower bounds can be specified:
  499. >>> idx = Idx(i, (L, U)); idx
  500. i
  501. >>> idx.lower, idx.upper
  502. (L, U)
  503. When only a single bound is given it is interpreted as the dimension
  504. and the lower bound defaults to 0:
  505. >>> idx = Idx(i, n); idx.lower, idx.upper
  506. (0, n - 1)
  507. >>> idx = Idx(i, 4); idx.lower, idx.upper
  508. (0, 3)
  509. >>> idx = Idx(i, oo); idx.lower, idx.upper
  510. (0, oo)
  511. """
  512. is_integer = True
  513. is_finite = True
  514. is_real = True
  515. is_symbol = True
  516. is_Atom = True
  517. _diff_wrt = True
  518. def __new__(cls, label, range=None, **kw_args):
  519. if isinstance(label, str):
  520. label = Symbol(label, integer=True)
  521. label, range = list(map(sympify, (label, range)))
  522. if label.is_Number:
  523. if not label.is_integer:
  524. raise TypeError("Index is not an integer number.")
  525. return label
  526. if not label.is_integer:
  527. raise TypeError("Idx object requires an integer label.")
  528. elif is_sequence(range):
  529. if len(range) != 2:
  530. raise ValueError(filldedent("""
  531. Idx range tuple must have length 2, but got %s""" % len(range)))
  532. for bound in range:
  533. if (bound.is_integer is False and bound is not S.Infinity
  534. and bound is not S.NegativeInfinity):
  535. raise TypeError("Idx object requires integer bounds.")
  536. args = label, Tuple(*range)
  537. elif isinstance(range, Expr):
  538. if range is not S.Infinity and fuzzy_not(range.is_integer):
  539. raise TypeError("Idx object requires an integer dimension.")
  540. args = label, Tuple(0, range - 1)
  541. elif range:
  542. raise TypeError(filldedent("""
  543. The range must be an ordered iterable or
  544. integer SymPy expression."""))
  545. else:
  546. args = label,
  547. obj = Expr.__new__(cls, *args, **kw_args)
  548. obj._assumptions["finite"] = True
  549. obj._assumptions["real"] = True
  550. return obj
  551. @property
  552. def label(self):
  553. """Returns the label (Integer or integer expression) of the Idx object.
  554. Examples
  555. ========
  556. >>> from sympy import Idx, Symbol
  557. >>> x = Symbol('x', integer=True)
  558. >>> Idx(x).label
  559. x
  560. >>> j = Symbol('j', integer=True)
  561. >>> Idx(j).label
  562. j
  563. >>> Idx(j + 1).label
  564. j + 1
  565. """
  566. return self.args[0]
  567. @property
  568. def lower(self):
  569. """Returns the lower bound of the ``Idx``.
  570. Examples
  571. ========
  572. >>> from sympy import Idx
  573. >>> Idx('j', 2).lower
  574. 0
  575. >>> Idx('j', 5).lower
  576. 0
  577. >>> Idx('j').lower is None
  578. True
  579. """
  580. try:
  581. return self.args[1][0]
  582. except IndexError:
  583. return
  584. @property
  585. def upper(self):
  586. """Returns the upper bound of the ``Idx``.
  587. Examples
  588. ========
  589. >>> from sympy import Idx
  590. >>> Idx('j', 2).upper
  591. 1
  592. >>> Idx('j', 5).upper
  593. 4
  594. >>> Idx('j').upper is None
  595. True
  596. """
  597. try:
  598. return self.args[1][1]
  599. except IndexError:
  600. return
  601. def _sympystr(self, p):
  602. return p.doprint(self.label)
  603. @property
  604. def name(self):
  605. return self.label.name if self.label.is_Symbol else str(self.label)
  606. @property
  607. def free_symbols(self):
  608. return {self}
  609. @dispatch(Idx, Idx)
  610. def _eval_is_ge(lhs, rhs): # noqa:F811
  611. other_upper = rhs if rhs.upper is None else rhs.upper
  612. other_lower = rhs if rhs.lower is None else rhs.lower
  613. if lhs.lower is not None and (lhs.lower >= other_upper) == True:
  614. return True
  615. if lhs.upper is not None and (lhs.upper < other_lower) == True:
  616. return False
  617. return None
  618. @dispatch(Idx, Number) # type:ignore
  619. def _eval_is_ge(lhs, rhs): # noqa:F811
  620. other_upper = rhs
  621. other_lower = rhs
  622. if lhs.lower is not None and (lhs.lower >= other_upper) == True:
  623. return True
  624. if lhs.upper is not None and (lhs.upper < other_lower) == True:
  625. return False
  626. return None
  627. @dispatch(Number, Idx) # type:ignore
  628. def _eval_is_ge(lhs, rhs): # noqa:F811
  629. other_upper = lhs
  630. other_lower = lhs
  631. if rhs.upper is not None and (rhs.upper <= other_lower) == True:
  632. return True
  633. if rhs.lower is not None and (rhs.lower > other_upper) == True:
  634. return False
  635. return None