Repository: sympy/sympy

Issue:
Cannot specify ODE initial conditions as just f(0)
Suppose you want to solve an ODE and solve for the constants in terms of the unevaluated value of a function at a given point. For example, solve `f(x).diff(x) - f(x)` and solve the constants at `f(0)`. I would expect something like this to work

```py
>>> dsolve(f(x).diff(x) - f(x), f(x), ics={f(0): f(0)})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/aaronmeurer/Documents/Python/sympy/sympy/./sympy/solvers/ode/ode.py", line 605, in dsolve
    hints = _desolve(eq, func=func,
  File "/Users/aaronmeurer/Documents/Python/sympy/sympy/./sympy/solvers/deutils.py", line 209, in _desolve
    hints = classifier(eq, func, dict=True, ics=ics, xi=xi, eta=eta,
  File "/Users/aaronmeurer/Documents/Python/sympy/sympy/./sympy/solvers/ode/ode.py", line 1013, in classify_ode
    raise ValueError("Enter valid boundary conditions for Function")
ValueError: Enter valid boundary conditions for Function
```

I would have expected this to give `Eq(f(x), f(0)*exp(x))`.

To do this, you have to create a separate symbol:

```py
>>> f0 = symbols('f0')
>>> dsolve(f(x).diff(x) - f(x), f(x), ics={f(0): f0})
Eq(f(x), f0*exp(x))
```

Discussion:

Patch:
diff --git a/sympy/solvers/ode/ode.py b/sympy/solvers/ode/ode.py
index 48d7492776..54cd390a7e 100644
--- a/sympy/solvers/ode/ode.py
+++ b/sympy/solvers/ode/ode.py
@@ -995,22 +995,22 @@ class in it.  Note that a hint may do this anyway if
                     AppliedUndef) and deriv.args[0].func == f and
                     len(deriv.args[0].args) == 1 and old == x and not
                     new.has(x) and all(i == deriv.variables[0] for i in
-                    deriv.variables) and not ics[funcarg].has(f)):
+                    deriv.variables) and x not in ics[funcarg].free_symbols):
 
                     dorder = ode_order(deriv, x)
                     temp = 'f' + str(dorder)
                     boundary.update({temp: new, temp + 'val': ics[funcarg]})
                 else:
-                    raise ValueError("Enter valid boundary conditions for Derivatives")
+                    raise ValueError("Invalid boundary conditions for Derivatives")
 
 
             # Separating functions
             elif isinstance(funcarg, AppliedUndef):
                 if (funcarg.func == f and len(funcarg.args) == 1 and
-                    not funcarg.args[0].has(x) and not ics[funcarg].has(f)):
+                    not funcarg.args[0].has(x) and x not in ics[funcarg].free_symbols):
                     boundary.update({'f0': funcarg.args[0], 'f0val': ics[funcarg]})
                 else:
-                    raise ValueError("Enter valid boundary conditions for Function")
+                    raise ValueError("Invalid boundary conditions for Function")
 
             else:
                 raise ValueError("Enter boundary conditions of the form ics={f(point): value, f(x).diff(x, order).subs(x, point): value}")


Test patch:
diff --git a/sympy/solvers/ode/tests/test_ode.py b/sympy/solvers/ode/tests/test_ode.py
index eb0b0c77e1..547b8e425c 100644
--- a/sympy/solvers/ode/tests/test_ode.py
+++ b/sympy/solvers/ode/tests/test_ode.py
@@ -351,11 +351,14 @@ def test_classify_ode_ics():
     ics = {f(0, 0): 1}
     raises(ValueError, lambda: classify_ode(eq, f(x), ics=ics))
 
-    # point contains f
-    # XXX: Should be NotImplementedError
-    ics = {f(0): f(1)}
+    # point contains x
+    ics = {f(0): f(x)}
     raises(ValueError, lambda: classify_ode(eq, f(x), ics=ics))
 
+    # Does not raise
+    ics = {f(0): f(0)}
+    classify_ode(eq, f(x), ics=ics)
+
     # Does not raise
     ics = {f(0): 1}
     classify_ode(eq, f(x), ics=ics)
@@ -385,11 +388,14 @@ def test_classify_ode_ics():
     ics = {Derivative(f(x), x, y).subs(x, 0): 1}
     raises(ValueError, lambda: classify_ode(eq, f(x), ics=ics))
 
-    # point contains f
-    # XXX: Should be NotImplementedError
-    ics = {f(x).diff(x).subs(x, 0): f(0)}
+    # point contains x
+    ics = {f(x).diff(x).subs(x, 0): f(x)}
     raises(ValueError, lambda: classify_ode(eq, f(x), ics=ics))
 
+    # Does not raise
+    ics = {f(x).diff(x).subs(x, 0): f(x).diff(x).subs(x, 0)}
+    classify_ode(eq, f(x), ics=ics)
+
     # Does not raise
     ics = {f(x).diff(x).subs(x, 0): 1}
     classify_ode(eq, f(x), ics=ics)
@@ -414,11 +420,14 @@ def test_classify_ode_ics():
     ics = {Derivative(f(x), x, z).subs(x, y): 1}
     raises(ValueError, lambda: classify_ode(eq, f(x), ics=ics))
 
-    # point contains f
-    # XXX: Should be NotImplementedError
-    ics = {f(x).diff(x).subs(x, y): f(0)}
+    # point contains x
+    ics = {f(x).diff(x).subs(x, y): f(x)}
     raises(ValueError, lambda: classify_ode(eq, f(x), ics=ics))
 
+    # Does not raise
+    ics = {f(x).diff(x).subs(x, 0): f(0)}
+    classify_ode(eq, f(x), ics=ics)
+
     # Does not raise
     ics = {f(x).diff(x).subs(x, y): 1}
     classify_ode(eq, f(x), ics=ics)
@@ -553,6 +562,13 @@ def test_solve_ics():
         C3: L**2*q/(4*EI),
         C4: -L*q/(6*EI)}
 
+    # Allow the ics to refer to f
+    ics = {f(0): f(0)}
+    assert dsolve(f(x).diff(x) - f(x), f(x), ics=ics) == Eq(f(x), f(0)*exp(x))
+
+    ics = {f(x).diff(x).subs(x, 0): f(x).diff(x).subs(x, 0), f(0): f(0)}
+    assert dsolve(f(x).diff(x, x) + f(x), f(x), ics=ics) == \
+        Eq(f(x), f(0)*cos(x) + f(x).diff(x).subs(x, 0)*sin(x))
 
 def test_ode_order():
     f = Function('f')