Repository: sympy/sympy
Pull request: KroneckerDelta simplification and rewrite(Piecewise)
#### References to other Issues or PRs
Closes #17214.

#### Brief description of what is fixed or changed
Add `_eval_rewrite_as_Piecewise` to `KroneckerDelta`. Add `simplify` recognition of expressions containing `KroneckerDelta` and attempt to simplify via `Piecewise`.

#### Release Notes

<!-- BEGIN RELEASE NOTES -->
* functions
  * KroneckerDelta can be rewritten as Piecewise.
  * Piecewise can be rewritten as KroneckerDelta
* simplify
  * simplify attempts to simplify expressions containing KroneckerDelta.
<!-- END RELEASE NOTES -->


Problem statement:
Simplify Mul of KroneckerDelta
There should be a way to simplify this to zero:
```julia
In [56]: n = Symbol('n', integer=True)                                                                                            

In [57]: expr = KroneckerDelta(0, n) * KroneckerDelta(1, n)                                                                       

In [58]: expr                                                                                                                     
Out[58]: 
δ   ⋅δ   
 0,n  1,n

In [59]: simplify(expr)                                                                                                           
Out[59]: 
δ   ⋅δ   
 0,n  1,n
```

Hints from issue discussion:

Commit message: add case for simplifying KroneckerDelta

Patch to review:
diff --git a/sympy/functions/special/tensor_functions.py b/sympy/functions/special/tensor_functions.py
index 66efe8c85762..18a056d47dbd 100644
--- a/sympy/functions/special/tensor_functions.py
+++ b/sympy/functions/special/tensor_functions.py
@@ -444,3 +444,9 @@ def indices(self):
     def _sage_(self):
         import sage.all as sage
         return sage.kronecker_delta(self.args[0]._sage_(), self.args[1]._sage_())
+
+    def _eval_rewrite_as_Piecewise(self, *args, **kwargs):
+        from sympy.functions.elementary.piecewise import Piecewise
+        from sympy.core.relational import Ne
+        i, j = args
+        return Piecewise((0, Ne(i, j)), (1, True))
diff --git a/sympy/functions/special/tests/test_tensor_functions.py b/sympy/functions/special/tests/test_tensor_functions.py
index 831a3d932b54..34047c0440f3 100644
--- a/sympy/functions/special/tests/test_tensor_functions.py
+++ b/sympy/functions/special/tests/test_tensor_functions.py
@@ -1,6 +1,6 @@
 from sympy import (
     adjoint, conjugate, Dummy, Eijk, KroneckerDelta, LeviCivita, Symbol,
-    symbols, transpose,
+    symbols, transpose, Piecewise, Ne
 )
 from sympy.core.compatibility import range, long
 from sympy.physics.secondquant import evaluate_deltas, F
@@ -61,6 +61,8 @@ def test_kronecker_delta():
     # to test if canonical
     assert (KroneckerDelta(i, j) == KroneckerDelta(j, i)) == True
 
+    assert KroneckerDelta(i, j).rewrite(Piecewise) == Piecewise((0, Ne(i, j)), (1, True))
+
 def test_kronecker_delta_secondquant():
     """secondquant-specific methods"""
     D = KroneckerDelta
diff --git a/sympy/simplify/simplify.py b/sympy/simplify/simplify.py
index a64187774d12..729270485971 100644
--- a/sympy/simplify/simplify.py
+++ b/sympy/simplify/simplify.py
@@ -19,6 +19,7 @@
 from sympy.functions.elementary.integers import ceiling
 from sympy.functions.elementary.trigonometric import TrigonometricFunction
 from sympy.functions.special.bessel import besselj, besseli, besselk, jn, bessely
+from sympy.functions.special.tensor_functions import KroneckerDelta
 from sympy.polys import together, cancel, factor
 from sympy.simplify.combsimp import combsimp
 from sympy.simplify.cse_opts import sub_pre, sub_post
@@ -614,6 +615,10 @@ def shorter(*choices):
     if expr.has(Product):
         expr = product_simplify(expr)
 
+    if expr.has(KroneckerDelta):
+        from sympy.functions.elementary.piecewise import Piecewise
+        expr = simplify(expr.rewrite(Piecewise))
+
     from sympy.physics.units import Quantity
     from sympy.physics.units.util import quantity_simplify
 
diff --git a/sympy/simplify/tests/test_simplify.py b/sympy/simplify/tests/test_simplify.py
index bd5a4e4391ab..c74a01873d20 100644
--- a/sympy/simplify/tests/test_simplify.py
+++ b/sympy/simplify/tests/test_simplify.py
@@ -3,8 +3,8 @@
     collect,cos, cosh, cot, coth, count_ops, csch, Derivative, diff, E,
     Eq, erf, exp, exp_polar, expand, expand_multinomial, factor,
     factorial, Float, fraction, Function, gamma, GoldenRatio, hyper,
-    hypersimp, I, Integral, integrate, log, logcombine, Lt, Matrix,
-    MatrixSymbol, Mul, nsimplify, O, oo, pi, Piecewise, posify, rad,
+    hypersimp, I, Integral, integrate, KroneckerDelta, log, logcombine, Lt,
+    Matrix, MatrixSymbol, Mul, nsimplify, O, oo, pi, Piecewise, posify, rad,
     Rational, root, S, separatevars, signsimp, simplify, sign, sin,
     sinc, sinh, solve, sqrt, Sum, Symbol, symbols, sympify, tan, tanh,
     zoo)
@@ -837,3 +837,19 @@ def test_issue_17141():
     else:
         raises(RuntimeError, lambda: simplify(2**acos(I+1)**2))
         raises(RuntimeError, lambda: simplify((2**acos(I+1)**2).rewrite('log')))
+
+
+def test_simplify_kroneckerdelta():
+    i, j = symbols("i j")
+
+    assert simplify(KroneckerDelta(i, j)) == KroneckerDelta(i, j)
+    assert simplify(KroneckerDelta(0, j)) == KroneckerDelta(0, j)
+    assert simplify(KroneckerDelta(i, 0)) == KroneckerDelta(i, 0)
+
+    # issue 17214
+    e1 = KroneckerDelta(0, j) * KroneckerDelta(1, j)
+    assert simplify(e1) == 0
+
+    n = Symbol('n', integer=True)
+    e2 = KroneckerDelta(0, n) * KroneckerDelta(1, n)
+    assert simplify(e2) == 0


Code review comments:
diff_hunk: @@ -614,6 +615,10 @@ def shorter(*choices):
     if expr.has(Product):
         expr = product_simplify(expr)
 
+    if expr.has(KroneckerDelta):
+        from sympy.functions.elementary.piecewise import Piecewise
+        expr = simplify(expr.rewrite(Piecewise))
path: sympy/simplify/simplify.py
text: @user1:
Is the call to simplify needed here? Maybe this could just be piecewise_fold instead of simplify. Or since piecewise_fold is called above maybe the rewrite could be moved above that...

@user1:
Also it looks as if it would be okay to import Piecewise at the top.