Spaces:
Sleeping
Sleeping
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. | |
""" | |
# TODO: (some ideas for improvement) | |
# | |
# o test and guarantee numpy compatibility | |
# - implement full support for broadcasting | |
# - strided arrays | |
# | |
# o more functions to analyze indexed expressions | |
# - identify standard constructs, e.g matrix-vector product in a subexpression | |
# | |
# o functions to generate component based arrays (numpy and sympy.Matrix) | |
# - generate a single array directly from Indexed | |
# - convert simple sub-expressions | |
# | |
# o sophisticated indexing (possibly in subclasses to preserve simplicity) | |
# - Idx with range smaller than dimension of Indexed | |
# - Idx with stepsize != 1 | |
# - Idx with step determined by function call | |
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())) | |
def name(self): | |
return str(self) | |
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 | |
def assumptions0(self): | |
return {k: v for k, v in self._assumptions.items() if v is not None} | |
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] | |
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:] | |
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 | |
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) | |
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)) | |
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 | |
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 | |
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 # Issue #8873 | |
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 | |
def name(self): | |
return self._name | |
def _hashable_content(self): | |
return super()._hashable_content() + tuple(sorted(self.assumptions0.items())) | |
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): | |
# Special case needed because M[*my_tuple] is a syntax error. | |
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) | |
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 | |
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 | |
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 | |
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 | |
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] | |
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 | |
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) | |
def name(self): | |
return self.label.name if self.label.is_Symbol else str(self.label) | |
def free_symbols(self): | |
return {self} | |
def _eval_is_ge(lhs, rhs): # noqa:F811 | |
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 | |
# type:ignore | |
def _eval_is_ge(lhs, rhs): # noqa:F811 | |
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 | |
# type:ignore | |
def _eval_is_ge(lhs, rhs): # noqa:F811 | |
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 | |