Repository: sympy/sympy
Pull request: Added support for naming of Indexed types and avoid passing unnecessary kw_args
#### References to other Issues or PRs
Fixes #14960 

#### Brief description of what is fixed or changed
* IndexedBase and Indexed types have been given a name attribute. 
* kw_args are not unnecessarily passed to Expr when creating an IndexedBase object
#### Release Notes
<!-- BEGIN RELEASE NOTES -->
* tensor
  * Added naming for IndexedBase and Indexed types
  * Avoid passing kw_args to Expr during IndexedBase object creation
<!-- END RELEASE NOTES -->

Problem statement:
`integrate` raised issue with variable of integration of type `Indexed`
This error was raised with the particular expression given below. 
```
from sympy import *
>>> x = Symbol('x')
>>> x1, x2 = Indexed('x', 1), Indexed('x', 2)
>>> pdf = exp(-x1**2/2 + x1 - x2**2/2 - S(1)/2)/(2*pi)
>>> integrate(pdf, (x1, -oo, oo))
...
AttributeError: 'Indexed' object has no attribute 'name'
```

Hints from issue discussion:
It seems that plain `Indexed` is not the proper type for these variables. It should probably be a subclass of both  `Symbol` and `Indexed`.
@jksuom Do you mean to use `x, y = symbols('x y', cls=Idx)` and then rest of the statements ?
Similar error results on doing so.
No, that is not what I mean. I mean that the type of the indexed symbols to be used in the stats module should probably be both `Indexed` and `Symbols`. The objects created by `symbols('x y', cls=Idx)` will be of class `Idx` only.
@jksuom Okay, so it's quite clear that this happens because objects of sole `Indexed` type do not have a name associated with them.
Would it be reasonable to add a name attribute to `Indexed` class ?
For example, if `y = Indexed('y', 4)` then `y.name` would be `y[4]`.
That looks feasible to me. `str` could be used.

Commit message: Add support for name of IndexedBase and Indexed types

Patch to review:
diff --git a/sympy/tensor/indexed.py b/sympy/tensor/indexed.py
index 241d16402193..ccb6492172b4 100644
--- a/sympy/tensor/indexed.py
+++ b/sympy/tensor/indexed.py
@@ -159,6 +159,14 @@ def __new__(cls, base, *args, **kw_args):
 
         return Expr.__new__(cls, base, *args, **kw_args)
 
+    @property
+    def name(self):
+        indices_str = "["
+        for index in self.args[1:-1]:
+            indices_str += str(index) + ","
+        indices_str += str(self.args[-1]) + "]"
+        return self.base.name + indices_str
+
     @property
     def _diff_wrt(self):
         """Allow derivatives with respect to an ``Indexed`` object."""
@@ -403,8 +411,13 @@ def __new__(cls, label, shape=None, **kw_args):
         obj._shape = shape
         obj._offset = offset
         obj._strides = strides
+        obj.__name__ = str(label)
         return obj
 
+    @property
+    def name(self):
+        return self.__name__
+
     def __getitem__(self, indices, **kw_args):
         if is_sequence(indices):
             # Special case needed because M[*my_tuple] is a syntax error.
diff --git a/sympy/tensor/tests/test_indexed.py b/sympy/tensor/tests/test_indexed.py
index 7ac122568cbb..5ac6b5706923 100644
--- a/sympy/tensor/tests/test_indexed.py
+++ b/sympy/tensor/tests/test_indexed.py
@@ -229,6 +229,7 @@ def test_Indexed_subs():
 def test_Indexed_properties():
     i, j = symbols('i j', integer=True)
     A = Indexed('A', i, j)
+    assert A.name == 'A[i,j]'
     assert A.rank == 2
     assert A.indices == (i, j)
     assert A.base == IndexedBase('A')


Code review comments:
diff_hunk: @@ -159,6 +159,14 @@ def __new__(cls, base, *args, **kw_args):
 
         return Expr.__new__(cls, base, *args, **kw_args)
 
+    @property
+    def name(self):
+        indices_str = "["
+        for index in self.args[1:-1]:
+            indices_str += str(index) + ","
+        indices_str += str(self.args[-1]) + "]"
+        return self.base.name + indices_str
path: sympy/tensor/indexed.py
text: @user1:
Would it suffice to return `str(self)`?

@author:
Yes, it does. Missed that earlier. Updated now.