Repository: sympy/sympy
Pull request: geometry/util: fixed idiff() for Function support
<!-- Your title above should be a short description of what
was changed. Do not include the issue number in the title. -->


#### References to other Issues or PRs
<!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
format, e.g. "Fixes #1234". See
https://github.com/blog/1506-closing-issues-via-pull-requests .-->
Fixes #15584 

#### Brief description of what is fixed or changed
Now idiff(eq, y, x, n=1) supports:

- Eq as its first argument.
	Example:
		In [1]: from sympy import *
		In [2]: x, y = symbols('x y')
		In [3]: print(idiff(Eq(y*exp(y), x*exp(x)), y, x))
		Out [3]: (x + 1)*exp(x - y)/(y + 1)

- f(x) instead of y, where f is a Function.
	Example:
		In [1]: from sympy import *
		In [2]: x = symbols('x')
		In [3]: f = Function('f')
		In [4]: print(idiff(f(x)*exp(f(x)) - x*exp(x), f(x), x))
		Out [4]: (x + 1)*exp(x - f(x))/(f(x) + 1)

#### Other comments


#### Release Notes

<!-- Write the release notes for this release below. See
https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more information
on how to write release notes. The bot will check your release notes
automatically to see if they are formatted correctly. -->

<!-- BEGIN RELEASE NOTES -->
* geometry
    * in idiff added support for the case when y is a Function
<!-- END RELEASE NOTES -->


Problem statement:
Some issues with idiff
idiff doesn't support Eq, and it also doesn't support f(x) instead of y. Both should be easy to correct.

```
>>> idiff(Eq(y*exp(y), x*exp(x)), y, x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "./sympy/geometry/util.py", line 582, in idiff
    yp = solve(eq.diff(x), dydx)[0].subs(derivs)
IndexError: list index out of range
>>> idiff(f(x)*exp(f(x)) - x*exp(x), f(x), x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "./sympy/geometry/util.py", line 574, in idiff
    raise ValueError("expecting x-dependent symbol(s) but got: %s" % y)
ValueError: expecting x-dependent symbol(s) but got: f(x)
>>> idiff(y*exp(y)- x*exp(x), y, x)
(x + 1)*exp(x - y)/(y + 1)
```

Hints from issue discussion:
Hi i am a beginner and i would like to work on this issue.
@krishna-akula are you still working on this?... I'd like to work on it too
See comments on #15678. I can see adding `f(x)` support but not Eq for reasons given there. See #5030
But `idiff` is in the class of functions that assumes the expression = 0 (see the docstring). So I think it makes sense to accept an Eq.

Commit message: Added tests for idiff()

Added 5 tests in test_idiff() in geometry/tests/test_util.py

Patch to review:
diff --git a/sympy/geometry/tests/test_util.py b/sympy/geometry/tests/test_util.py
index a6ded4406202..1ea72584cd39 100644
--- a/sympy/geometry/tests/test_util.py
+++ b/sympy/geometry/tests/test_util.py
@@ -1,5 +1,5 @@
-from sympy import Symbol, sqrt, Derivative, S
-from sympy.geometry import Point, Point2D, Line, Circle ,Polygon, Segment, convex_hull, intersection, centroid
+from sympy import Symbol, sqrt, Derivative, S, Function, exp, Eq
+from sympy.geometry import Point, Point2D, Line, Circle, Polygon, Segment, convex_hull, intersection, centroid
 from sympy.geometry.util import idiff, closest_points, farthest_points, _ordered_points
 from sympy.solvers.solvers import solve
 from sympy.utilities.pytest import raises
@@ -9,6 +9,8 @@ def test_idiff():
     x = Symbol('x', real=True)
     y = Symbol('y', real=True)
     t = Symbol('t', real=True)
+    f = Function('f')
+    g = Function('g')
     # the use of idiff in ellipse also provides coverage
     circ = x**2 + y**2 - 4
     ans = -3*x*(x**2 + y**2)/y**5
@@ -19,6 +21,11 @@ def test_idiff():
     assert ans.subs(y, solve(circ, y)[0]).equals(explicit)
     assert True in [sol.diff(x, 3).equals(explicit) for sol in solve(circ, y)]
     assert idiff(x + t + y, [y, t], x) == -Derivative(t, x) - 1
+    assert idiff(f(x) * exp(f(x)) - x * exp(x), f(x), x) == (x + 1) * exp(x - f(x))/(f(x) + 1)
+    assert idiff(f(x) - y * exp(x), [f(x), y], x) == (y + Derivative(y, x)) * exp(x)
+    assert idiff(f(x) - y * exp(x), [y, f(x)], x) == -y + exp(-x) * Derivative(f(x), x)
+    assert idiff(Eq(y * exp(y), x * exp(x)), y, x) == (x + 1) * exp(x - y)/(y + 1)
+    assert idiff(Eq(f(x), g(x)), [f(x), g(x)], x) == Derivative(g(x), x)
 
 
 def test_intersection():
diff --git a/sympy/geometry/util.py b/sympy/geometry/util.py
index d32adc3066ed..b909763fd7d4 100644
--- a/sympy/geometry/util.py
+++ b/sympy/geometry/util.py
@@ -17,6 +17,7 @@
     is_sequence, range, string_types, ordered)
 from sympy.core.containers import OrderedSet
 from .point import Point, Point2D
+from  sympy.core import Equality, Add
 
 
 def find(x, equation):
@@ -570,12 +571,22 @@ def idiff(eq, y, x, n=1):
         y = y[0]
     elif isinstance(y, Symbol):
         dep = {y}
+    elif isinstance(y, Function):
+        pass
     else:
-        raise ValueError("expecting x-dependent symbol(s) but got: %s" % y)
+        raise ValueError("expecting x-dependent symbol(s) or function(s) but got: %s" % y)
+
+    if isinstance(eq, Equality):
+        eq = eq.rewrite(Add)
 
     f = dict([(s, Function(
         s.name)(x)) for s in eq.free_symbols if s != x and s in dep])
-    dydx = Function(y.name)(x).diff(x)
+
+    if isinstance(y, Symbol):
+        dydx = Function(y.name)(x).diff(x)
+    else:
+        dydx = y.diff(x)
+
     eq = eq.subs(f)
     derivs = {}
     for i in range(n):


Code review comments:
diff_hunk: @@ -570,12 +571,22 @@ def idiff(eq, y, x, n=1):
         y = y[0]
     elif isinstance(y, Symbol):
         dep = {y}
+    elif isinstance(y, Function):
+        pass
     else:
-        raise ValueError("expecting x-dependent symbol(s) but got: %s" % y)
+        raise ValueError("expecting x-dependent symbol(s) or function(s) but got: %s" % y)
+
+    if isinstance(eq, Equality):
path: sympy/geometry/util.py
text: @user1:
oops! I missed this. A derivative of a bool is something that might be defined (see [here](https://github.com/sympy/sympy/issues/13350)) but if you use the `Eq` as an equation container in this routine, it will never be able to calculate a derivative of this boolean. If the *user* wants to use `Eq` as an equation container, that is their choice; if they want to use `idiff` *they* will have to pass it as `eq.rewrite(Add)`; this is not something that `idiff` should do.