|
r"""Module that defines indexed objects. |
|
|
|
The classes ``IndexedBase``, ``Indexed``, and ``Idx`` represent a |
|
matrix element ``M[i, j]`` as in the following diagram:: |
|
|
|
1) The Indexed class represents the entire indexed object. |
|
| |
|
___|___ |
|
' ' |
|
M[i, j] |
|
/ \__\______ |
|
| | |
|
| | |
|
| 2) The Idx class represents indices; each Idx can |
|
| optionally contain information about its range. |
|
| |
|
3) IndexedBase represents the 'stem' of an indexed object, here `M`. |
|
The stem used by itself is usually taken to represent the entire |
|
array. |
|
|
|
There can be any number of indices on an Indexed object. No |
|
transformation properties are implemented in these Base objects, but |
|
implicit contraction of repeated indices is supported. |
|
|
|
Note that the support for complicated (i.e. non-atomic) integer |
|
expressions as indices is limited. (This should be improved in |
|
future releases.) |
|
|
|
Examples |
|
======== |
|
|
|
To express the above matrix element example you would write: |
|
|
|
>>> from sympy import symbols, IndexedBase, Idx |
|
>>> M = IndexedBase('M') |
|
>>> i, j = symbols('i j', cls=Idx) |
|
>>> M[i, j] |
|
M[i, j] |
|
|
|
Repeated indices in a product implies a summation, so to express a |
|
matrix-vector product in terms of Indexed objects: |
|
|
|
>>> x = IndexedBase('x') |
|
>>> M[i, j]*x[j] |
|
M[i, j]*x[j] |
|
|
|
If the indexed objects will be converted to component based arrays, e.g. |
|
with the code printers or the autowrap framework, you also need to provide |
|
(symbolic or numerical) dimensions. This can be done by passing an |
|
optional shape parameter to IndexedBase upon construction: |
|
|
|
>>> dim1, dim2 = symbols('dim1 dim2', integer=True) |
|
>>> A = IndexedBase('A', shape=(dim1, 2*dim1, dim2)) |
|
>>> A.shape |
|
(dim1, 2*dim1, dim2) |
|
>>> A[i, j, 3].shape |
|
(dim1, 2*dim1, dim2) |
|
|
|
If an IndexedBase object has no shape information, it is assumed that the |
|
array is as large as the ranges of its indices: |
|
|
|
>>> n, m = symbols('n m', integer=True) |
|
>>> i = Idx('i', m) |
|
>>> j = Idx('j', n) |
|
>>> M[i, j].shape |
|
(m, n) |
|
>>> M[i, j].ranges |
|
[(0, m - 1), (0, n - 1)] |
|
|
|
The above can be compared with the following: |
|
|
|
>>> A[i, 2, j].shape |
|
(dim1, 2*dim1, dim2) |
|
>>> A[i, 2, j].ranges |
|
[(0, m - 1), None, (0, n - 1)] |
|
|
|
To analyze the structure of indexed expressions, you can use the methods |
|
get_indices() and get_contraction_structure(): |
|
|
|
>>> from sympy.tensor import get_indices, get_contraction_structure |
|
>>> get_indices(A[i, j, j]) |
|
({i}, {}) |
|
>>> get_contraction_structure(A[i, j, j]) |
|
{(j,): {A[i, j, j]}} |
|
|
|
See the appropriate docstrings for a detailed explanation of the output. |
|
""" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from collections.abc import Iterable |
|
|
|
from sympy.core.numbers import Number |
|
from sympy.core.assumptions import StdFactKB |
|
from sympy.core import Expr, Tuple, sympify, S |
|
from sympy.core.symbol import _filter_assumptions, Symbol |
|
from sympy.core.logic import fuzzy_bool, fuzzy_not |
|
from sympy.core.sympify import _sympify |
|
from sympy.functions.special.tensor_functions import KroneckerDelta |
|
from sympy.multipledispatch import dispatch |
|
from sympy.utilities.iterables import is_sequence, NotIterable |
|
from sympy.utilities.misc import filldedent |
|
|
|
|
|
class IndexException(Exception): |
|
pass |
|
|
|
|
|
class Indexed(Expr): |
|
"""Represents a mathematical object with indices. |
|
|
|
>>> from sympy import Indexed, IndexedBase, Idx, symbols |
|
>>> i, j = symbols('i j', cls=Idx) |
|
>>> Indexed('A', i, j) |
|
A[i, j] |
|
|
|
It is recommended that ``Indexed`` objects be created by indexing ``IndexedBase``: |
|
``IndexedBase('A')[i, j]`` instead of ``Indexed(IndexedBase('A'), i, j)``. |
|
|
|
>>> A = IndexedBase('A') |
|
>>> a_ij = A[i, j] # Prefer this, |
|
>>> b_ij = Indexed(A, i, j) # over this. |
|
>>> a_ij == b_ij |
|
True |
|
|
|
""" |
|
is_Indexed = True |
|
is_symbol = True |
|
is_Atom = True |
|
|
|
def __new__(cls, base, *args, **kw_args): |
|
from sympy.tensor.array.ndim_array import NDimArray |
|
from sympy.matrices.matrixbase import MatrixBase |
|
|
|
if not args: |
|
raise IndexException("Indexed needs at least one index.") |
|
if isinstance(base, (str, Symbol)): |
|
base = IndexedBase(base) |
|
elif not hasattr(base, '__getitem__') and not isinstance(base, IndexedBase): |
|
raise TypeError(filldedent(""" |
|
The base can only be replaced with a string, Symbol, |
|
IndexedBase or an object with a method for getting |
|
items (i.e. an object with a `__getitem__` method). |
|
""")) |
|
args = list(map(sympify, args)) |
|
if isinstance(base, (NDimArray, Iterable, Tuple, MatrixBase)) and all(i.is_number for i in args): |
|
if len(args) == 1: |
|
return base[args[0]] |
|
else: |
|
return base[args] |
|
|
|
base = _sympify(base) |
|
|
|
obj = Expr.__new__(cls, base, *args, **kw_args) |
|
|
|
IndexedBase._set_assumptions(obj, base.assumptions0) |
|
|
|
return obj |
|
|
|
def _hashable_content(self): |
|
return super()._hashable_content() + tuple(sorted(self.assumptions0.items())) |
|
|
|
@property |
|
def name(self): |
|
return str(self) |
|
|
|
@property |
|
def _diff_wrt(self): |
|
"""Allow derivatives with respect to an ``Indexed`` object.""" |
|
return True |
|
|
|
def _eval_derivative(self, wrt): |
|
from sympy.tensor.array.ndim_array import NDimArray |
|
|
|
if isinstance(wrt, Indexed) and wrt.base == self.base: |
|
if len(self.indices) != len(wrt.indices): |
|
msg = "Different # of indices: d({!s})/d({!s})".format(self, |
|
wrt) |
|
raise IndexException(msg) |
|
result = S.One |
|
for index1, index2 in zip(self.indices, wrt.indices): |
|
result *= KroneckerDelta(index1, index2) |
|
return result |
|
elif isinstance(self.base, NDimArray): |
|
from sympy.tensor.array import derive_by_array |
|
return Indexed(derive_by_array(self.base, wrt), *self.args[1:]) |
|
else: |
|
if Tuple(self.indices).has(wrt): |
|
return S.NaN |
|
return S.Zero |
|
|
|
@property |
|
def assumptions0(self): |
|
return {k: v for k, v in self._assumptions.items() if v is not None} |
|
|
|
@property |
|
def base(self): |
|
"""Returns the ``IndexedBase`` of the ``Indexed`` object. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Indexed, IndexedBase, Idx, symbols |
|
>>> i, j = symbols('i j', cls=Idx) |
|
>>> Indexed('A', i, j).base |
|
A |
|
>>> B = IndexedBase('B') |
|
>>> B == B[i, j].base |
|
True |
|
|
|
""" |
|
return self.args[0] |
|
|
|
@property |
|
def indices(self): |
|
""" |
|
Returns the indices of the ``Indexed`` object. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Indexed, Idx, symbols |
|
>>> i, j = symbols('i j', cls=Idx) |
|
>>> Indexed('A', i, j).indices |
|
(i, j) |
|
|
|
""" |
|
return self.args[1:] |
|
|
|
@property |
|
def rank(self): |
|
""" |
|
Returns the rank of the ``Indexed`` object. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Indexed, Idx, symbols |
|
>>> i, j, k, l, m = symbols('i:m', cls=Idx) |
|
>>> Indexed('A', i, j).rank |
|
2 |
|
>>> q = Indexed('A', i, j, k, l, m) |
|
>>> q.rank |
|
5 |
|
>>> q.rank == len(q.indices) |
|
True |
|
|
|
""" |
|
return len(self.args) - 1 |
|
|
|
@property |
|
def shape(self): |
|
"""Returns a list with dimensions of each index. |
|
|
|
Dimensions is a property of the array, not of the indices. Still, if |
|
the ``IndexedBase`` does not define a shape attribute, it is assumed |
|
that the ranges of the indices correspond to the shape of the array. |
|
|
|
>>> from sympy import IndexedBase, Idx, symbols |
|
>>> n, m = symbols('n m', integer=True) |
|
>>> i = Idx('i', m) |
|
>>> j = Idx('j', m) |
|
>>> A = IndexedBase('A', shape=(n, n)) |
|
>>> B = IndexedBase('B') |
|
>>> A[i, j].shape |
|
(n, n) |
|
>>> B[i, j].shape |
|
(m, m) |
|
""" |
|
|
|
if self.base.shape: |
|
return self.base.shape |
|
sizes = [] |
|
for i in self.indices: |
|
upper = getattr(i, 'upper', None) |
|
lower = getattr(i, 'lower', None) |
|
if None in (upper, lower): |
|
raise IndexException(filldedent(""" |
|
Range is not defined for all indices in: %s""" % self)) |
|
try: |
|
size = upper - lower + 1 |
|
except TypeError: |
|
raise IndexException(filldedent(""" |
|
Shape cannot be inferred from Idx with |
|
undefined range: %s""" % self)) |
|
sizes.append(size) |
|
return Tuple(*sizes) |
|
|
|
@property |
|
def ranges(self): |
|
"""Returns a list of tuples with lower and upper range of each index. |
|
|
|
If an index does not define the data members upper and lower, the |
|
corresponding slot in the list contains ``None`` instead of a tuple. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Indexed,Idx, symbols |
|
>>> Indexed('A', Idx('i', 2), Idx('j', 4), Idx('k', 8)).ranges |
|
[(0, 1), (0, 3), (0, 7)] |
|
>>> Indexed('A', Idx('i', 3), Idx('j', 3), Idx('k', 3)).ranges |
|
[(0, 2), (0, 2), (0, 2)] |
|
>>> x, y, z = symbols('x y z', integer=True) |
|
>>> Indexed('A', x, y, z).ranges |
|
[None, None, None] |
|
|
|
""" |
|
ranges = [] |
|
sentinel = object() |
|
for i in self.indices: |
|
upper = getattr(i, 'upper', sentinel) |
|
lower = getattr(i, 'lower', sentinel) |
|
if sentinel not in (upper, lower): |
|
ranges.append((lower, upper)) |
|
else: |
|
ranges.append(None) |
|
return ranges |
|
|
|
def _sympystr(self, p): |
|
indices = list(map(p.doprint, self.indices)) |
|
return "%s[%s]" % (p.doprint(self.base), ", ".join(indices)) |
|
|
|
@property |
|
def free_symbols(self): |
|
base_free_symbols = self.base.free_symbols |
|
indices_free_symbols = { |
|
fs for i in self.indices for fs in i.free_symbols} |
|
if base_free_symbols: |
|
return {self} | base_free_symbols | indices_free_symbols |
|
else: |
|
return indices_free_symbols |
|
|
|
@property |
|
def expr_free_symbols(self): |
|
from sympy.utilities.exceptions import sympy_deprecation_warning |
|
sympy_deprecation_warning(""" |
|
The expr_free_symbols property is deprecated. Use free_symbols to get |
|
the free symbols of an expression. |
|
""", |
|
deprecated_since_version="1.9", |
|
active_deprecations_target="deprecated-expr-free-symbols") |
|
|
|
return {self} |
|
|
|
|
|
class IndexedBase(Expr, NotIterable): |
|
"""Represent the base or stem of an indexed object |
|
|
|
The IndexedBase class represent an array that contains elements. The main purpose |
|
of this class is to allow the convenient creation of objects of the Indexed |
|
class. The __getitem__ method of IndexedBase returns an instance of |
|
Indexed. Alone, without indices, the IndexedBase class can be used as a |
|
notation for e.g. matrix equations, resembling what you could do with the |
|
Symbol class. But, the IndexedBase class adds functionality that is not |
|
available for Symbol instances: |
|
|
|
- An IndexedBase object can optionally store shape information. This can |
|
be used in to check array conformance and conditions for numpy |
|
broadcasting. (TODO) |
|
- An IndexedBase object implements syntactic sugar that allows easy symbolic |
|
representation of array operations, using implicit summation of |
|
repeated indices. |
|
- The IndexedBase object symbolizes a mathematical structure equivalent |
|
to arrays, and is recognized as such for code generation and automatic |
|
compilation and wrapping. |
|
|
|
>>> from sympy.tensor import IndexedBase, Idx |
|
>>> from sympy import symbols |
|
>>> A = IndexedBase('A'); A |
|
A |
|
>>> type(A) |
|
<class 'sympy.tensor.indexed.IndexedBase'> |
|
|
|
When an IndexedBase object receives indices, it returns an array with named |
|
axes, represented by an Indexed object: |
|
|
|
>>> i, j = symbols('i j', integer=True) |
|
>>> A[i, j, 2] |
|
A[i, j, 2] |
|
>>> type(A[i, j, 2]) |
|
<class 'sympy.tensor.indexed.Indexed'> |
|
|
|
The IndexedBase constructor takes an optional shape argument. If given, |
|
it overrides any shape information in the indices. (But not the index |
|
ranges!) |
|
|
|
>>> m, n, o, p = symbols('m n o p', integer=True) |
|
>>> i = Idx('i', m) |
|
>>> j = Idx('j', n) |
|
>>> A[i, j].shape |
|
(m, n) |
|
>>> B = IndexedBase('B', shape=(o, p)) |
|
>>> B[i, j].shape |
|
(o, p) |
|
|
|
Assumptions can be specified with keyword arguments the same way as for Symbol: |
|
|
|
>>> A_real = IndexedBase('A', real=True) |
|
>>> A_real.is_real |
|
True |
|
>>> A != A_real |
|
True |
|
|
|
Assumptions can also be inherited if a Symbol is used to initialize the IndexedBase: |
|
|
|
>>> I = symbols('I', integer=True) |
|
>>> C_inherit = IndexedBase(I) |
|
>>> C_explicit = IndexedBase('I', integer=True) |
|
>>> C_inherit == C_explicit |
|
True |
|
""" |
|
is_symbol = True |
|
is_Atom = True |
|
|
|
@staticmethod |
|
def _set_assumptions(obj, assumptions): |
|
"""Set assumptions on obj, making sure to apply consistent values.""" |
|
tmp_asm_copy = assumptions.copy() |
|
is_commutative = fuzzy_bool(assumptions.get('commutative', True)) |
|
assumptions['commutative'] = is_commutative |
|
obj._assumptions = StdFactKB(assumptions) |
|
obj._assumptions._generator = tmp_asm_copy |
|
|
|
def __new__(cls, label, shape=None, *, offset=S.Zero, strides=None, **kw_args): |
|
from sympy.matrices.matrixbase import MatrixBase |
|
from sympy.tensor.array.ndim_array import NDimArray |
|
|
|
assumptions, kw_args = _filter_assumptions(kw_args) |
|
if isinstance(label, str): |
|
label = Symbol(label, **assumptions) |
|
elif isinstance(label, Symbol): |
|
assumptions = label._merge(assumptions) |
|
elif isinstance(label, (MatrixBase, NDimArray)): |
|
return label |
|
elif isinstance(label, Iterable): |
|
return _sympify(label) |
|
else: |
|
label = _sympify(label) |
|
|
|
if is_sequence(shape): |
|
shape = Tuple(*shape) |
|
elif shape is not None: |
|
shape = Tuple(shape) |
|
|
|
if shape is not None: |
|
obj = Expr.__new__(cls, label, shape) |
|
else: |
|
obj = Expr.__new__(cls, label) |
|
obj._shape = shape |
|
obj._offset = offset |
|
obj._strides = strides |
|
obj._name = str(label) |
|
|
|
IndexedBase._set_assumptions(obj, assumptions) |
|
return obj |
|
|
|
@property |
|
def name(self): |
|
return self._name |
|
|
|
def _hashable_content(self): |
|
return super()._hashable_content() + tuple(sorted(self.assumptions0.items())) |
|
|
|
@property |
|
def assumptions0(self): |
|
return {k: v for k, v in self._assumptions.items() if v is not None} |
|
|
|
def __getitem__(self, indices, **kw_args): |
|
if is_sequence(indices): |
|
|
|
if self.shape and len(self.shape) != len(indices): |
|
raise IndexException("Rank mismatch.") |
|
return Indexed(self, *indices, **kw_args) |
|
else: |
|
if self.shape and len(self.shape) != 1: |
|
raise IndexException("Rank mismatch.") |
|
return Indexed(self, indices, **kw_args) |
|
|
|
@property |
|
def shape(self): |
|
"""Returns the shape of the ``IndexedBase`` object. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import IndexedBase, Idx |
|
>>> from sympy.abc import x, y |
|
>>> IndexedBase('A', shape=(x, y)).shape |
|
(x, y) |
|
|
|
Note: If the shape of the ``IndexedBase`` is specified, it will override |
|
any shape information given by the indices. |
|
|
|
>>> A = IndexedBase('A', shape=(x, y)) |
|
>>> B = IndexedBase('B') |
|
>>> i = Idx('i', 2) |
|
>>> j = Idx('j', 1) |
|
>>> A[i, j].shape |
|
(x, y) |
|
>>> B[i, j].shape |
|
(2, 1) |
|
|
|
""" |
|
return self._shape |
|
|
|
@property |
|
def strides(self): |
|
"""Returns the strided scheme for the ``IndexedBase`` object. |
|
|
|
Normally this is a tuple denoting the number of |
|
steps to take in the respective dimension when traversing |
|
an array. For code generation purposes strides='C' and |
|
strides='F' can also be used. |
|
|
|
strides='C' would mean that code printer would unroll |
|
in row-major order and 'F' means unroll in column major |
|
order. |
|
|
|
""" |
|
|
|
return self._strides |
|
|
|
@property |
|
def offset(self): |
|
"""Returns the offset for the ``IndexedBase`` object. |
|
|
|
This is the value added to the resulting index when the |
|
2D Indexed object is unrolled to a 1D form. Used in code |
|
generation. |
|
|
|
Examples |
|
========== |
|
>>> from sympy.printing import ccode |
|
>>> from sympy.tensor import IndexedBase, Idx |
|
>>> from sympy import symbols |
|
>>> l, m, n, o = symbols('l m n o', integer=True) |
|
>>> A = IndexedBase('A', strides=(l, m, n), offset=o) |
|
>>> i, j, k = map(Idx, 'ijk') |
|
>>> ccode(A[i, j, k]) |
|
'A[l*i + m*j + n*k + o]' |
|
|
|
""" |
|
return self._offset |
|
|
|
@property |
|
def label(self): |
|
"""Returns the label of the ``IndexedBase`` object. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import IndexedBase |
|
>>> from sympy.abc import x, y |
|
>>> IndexedBase('A', shape=(x, y)).label |
|
A |
|
|
|
""" |
|
return self.args[0] |
|
|
|
def _sympystr(self, p): |
|
return p.doprint(self.label) |
|
|
|
|
|
class Idx(Expr): |
|
"""Represents an integer index as an ``Integer`` or integer expression. |
|
|
|
There are a number of ways to create an ``Idx`` object. The constructor |
|
takes two arguments: |
|
|
|
``label`` |
|
An integer or a symbol that labels the index. |
|
``range`` |
|
Optionally you can specify a range as either |
|
|
|
* ``Symbol`` or integer: This is interpreted as a dimension. Lower and |
|
upper bounds are set to ``0`` and ``range - 1``, respectively. |
|
* ``tuple``: The two elements are interpreted as the lower and upper |
|
bounds of the range, respectively. |
|
|
|
Note: bounds of the range are assumed to be either integer or infinite (oo |
|
and -oo are allowed to specify an unbounded range). If ``n`` is given as a |
|
bound, then ``n.is_integer`` must not return false. |
|
|
|
For convenience, if the label is given as a string it is automatically |
|
converted to an integer symbol. (Note: this conversion is not done for |
|
range or dimension arguments.) |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Idx, symbols, oo |
|
>>> n, i, L, U = symbols('n i L U', integer=True) |
|
|
|
If a string is given for the label an integer ``Symbol`` is created and the |
|
bounds are both ``None``: |
|
|
|
>>> idx = Idx('qwerty'); idx |
|
qwerty |
|
>>> idx.lower, idx.upper |
|
(None, None) |
|
|
|
Both upper and lower bounds can be specified: |
|
|
|
>>> idx = Idx(i, (L, U)); idx |
|
i |
|
>>> idx.lower, idx.upper |
|
(L, U) |
|
|
|
When only a single bound is given it is interpreted as the dimension |
|
and the lower bound defaults to 0: |
|
|
|
>>> idx = Idx(i, n); idx.lower, idx.upper |
|
(0, n - 1) |
|
>>> idx = Idx(i, 4); idx.lower, idx.upper |
|
(0, 3) |
|
>>> idx = Idx(i, oo); idx.lower, idx.upper |
|
(0, oo) |
|
|
|
""" |
|
|
|
is_integer = True |
|
is_finite = True |
|
is_real = True |
|
is_symbol = True |
|
is_Atom = True |
|
_diff_wrt = True |
|
|
|
def __new__(cls, label, range=None, **kw_args): |
|
|
|
if isinstance(label, str): |
|
label = Symbol(label, integer=True) |
|
label, range = list(map(sympify, (label, range))) |
|
|
|
if label.is_Number: |
|
if not label.is_integer: |
|
raise TypeError("Index is not an integer number.") |
|
return label |
|
|
|
if not label.is_integer: |
|
raise TypeError("Idx object requires an integer label.") |
|
|
|
elif is_sequence(range): |
|
if len(range) != 2: |
|
raise ValueError(filldedent(""" |
|
Idx range tuple must have length 2, but got %s""" % len(range))) |
|
for bound in range: |
|
if (bound.is_integer is False and bound is not S.Infinity |
|
and bound is not S.NegativeInfinity): |
|
raise TypeError("Idx object requires integer bounds.") |
|
args = label, Tuple(*range) |
|
elif isinstance(range, Expr): |
|
if range is not S.Infinity and fuzzy_not(range.is_integer): |
|
raise TypeError("Idx object requires an integer dimension.") |
|
args = label, Tuple(0, range - 1) |
|
elif range: |
|
raise TypeError(filldedent(""" |
|
The range must be an ordered iterable or |
|
integer SymPy expression.""")) |
|
else: |
|
args = label, |
|
|
|
obj = Expr.__new__(cls, *args, **kw_args) |
|
obj._assumptions["finite"] = True |
|
obj._assumptions["real"] = True |
|
return obj |
|
|
|
@property |
|
def label(self): |
|
"""Returns the label (Integer or integer expression) of the Idx object. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Idx, Symbol |
|
>>> x = Symbol('x', integer=True) |
|
>>> Idx(x).label |
|
x |
|
>>> j = Symbol('j', integer=True) |
|
>>> Idx(j).label |
|
j |
|
>>> Idx(j + 1).label |
|
j + 1 |
|
|
|
""" |
|
return self.args[0] |
|
|
|
@property |
|
def lower(self): |
|
"""Returns the lower bound of the ``Idx``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Idx |
|
>>> Idx('j', 2).lower |
|
0 |
|
>>> Idx('j', 5).lower |
|
0 |
|
>>> Idx('j').lower is None |
|
True |
|
|
|
""" |
|
try: |
|
return self.args[1][0] |
|
except IndexError: |
|
return |
|
|
|
@property |
|
def upper(self): |
|
"""Returns the upper bound of the ``Idx``. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Idx |
|
>>> Idx('j', 2).upper |
|
1 |
|
>>> Idx('j', 5).upper |
|
4 |
|
>>> Idx('j').upper is None |
|
True |
|
|
|
""" |
|
try: |
|
return self.args[1][1] |
|
except IndexError: |
|
return |
|
|
|
def _sympystr(self, p): |
|
return p.doprint(self.label) |
|
|
|
@property |
|
def name(self): |
|
return self.label.name if self.label.is_Symbol else str(self.label) |
|
|
|
@property |
|
def free_symbols(self): |
|
return {self} |
|
|
|
|
|
@dispatch(Idx, Idx) |
|
def _eval_is_ge(lhs, rhs): |
|
|
|
other_upper = rhs if rhs.upper is None else rhs.upper |
|
other_lower = rhs if rhs.lower is None else rhs.lower |
|
|
|
if lhs.lower is not None and (lhs.lower >= other_upper) == True: |
|
return True |
|
if lhs.upper is not None and (lhs.upper < other_lower) == True: |
|
return False |
|
return None |
|
|
|
|
|
@dispatch(Idx, Number) |
|
def _eval_is_ge(lhs, rhs): |
|
|
|
other_upper = rhs |
|
other_lower = rhs |
|
|
|
if lhs.lower is not None and (lhs.lower >= other_upper) == True: |
|
return True |
|
if lhs.upper is not None and (lhs.upper < other_lower) == True: |
|
return False |
|
return None |
|
|
|
|
|
@dispatch(Number, Idx) |
|
def _eval_is_ge(lhs, rhs): |
|
|
|
other_upper = lhs |
|
other_lower = lhs |
|
|
|
if rhs.upper is not None and (rhs.upper <= other_lower) == True: |
|
return True |
|
if rhs.lower is not None and (rhs.lower > other_upper) == True: |
|
return False |
|
return None |
|
|