Repository: sympy/sympy
Pull request: Fixed `FiniteSet.evalf(...)` with a subs argument
<!-- 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://tinyurl.com/auto-closing for more information). Also, please
write a comment on that issue linking back to this pull request once it is
open. -->
Fixes #20291 


#### Brief description of what is fixed or changed

`FiniteSet(a).evalf(subs={a: 1})` would return `FiniteSet(a)` instead of `FiniteSet(1)`. This PR fixes this and similar issues.

#### 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 -->
* sets
    * `FiniteSet.evalf` with a subs argument now does the substitution. Previously, the argument was ignored.
<!-- END RELEASE NOTES -->

Problem statement:
[bug] subs option to evalf isn't working for sets
Evalf does not seem to work on solveset. 

Code to replicate:
```
from sympy.stats import Uniform, cdf
from sympy import Symbol, Eq, solveset, solve

x = Symbol("x")
a = Symbol("a")
b = Symbol("b")

D = Uniform("d", a, b)
F_x = cdf(D)(x)

u = Symbol("u")
expr = solveset(Eq(F_x, u), x)
expr.evalf(subs={u:0.6, a: 0, b: 2})
>> returns {a - u*(a - b)}
```

If instead I use solve, it works:

```
expr = solve(Eq(F_x, u), x)[0]
expr.evalf(subs={u:0.6, a: 0, b: 2})
>> returns 1.20000000000000
```

Hints from issue discussion:
Looks like `Set.evalf` with a subs argument does not work. That is, `FiniteSet(a).evalf(subs={a: 1})` returns `FiniteSet(a)`. As a workaround for now, I suggest you do `expr.subs({u: 0.6, a: 0, b: 2}).evalf()`.

I'll have a PR out as soon as I know my changes don't break the build somewhere else.

Note that between your version and master, that cdf has become piecewise. SymPy is unable to solve for `x` and just throws an error. Hopefully by 1.8, there will be no more piecewise pdf's, pmf's and cdf's.

Commit message: Added `isinstance(self, Basic)` check

Patch to review:
diff --git a/sympy/core/evalf.py b/sympy/core/evalf.py
index 812e9de5f8d6..862d7385e4ee 100644
--- a/sympy/core/evalf.py
+++ b/sympy/core/evalf.py
@@ -1473,7 +1473,11 @@ def evalf(self, n=15, subs=None, maxn=100, chop=False, strict=False, quad=None,
             result = evalf(self, prec + 4, options)
         except NotImplementedError:
             # Fall back to the ordinary evalf
-            v = self._eval_evalf(prec)
+            from sympy.core.basic import Basic
+            if isinstance(self, Basic) and subs is not None:  # issue 20291
+                v = self.subs(subs)._eval_evalf(prec)
+            else:
+                v = self._eval_evalf(prec)
             if v is None:
                 return self
             elif not v.is_number:
diff --git a/sympy/core/tests/test_evalf.py b/sympy/core/tests/test_evalf.py
index dc3a1663ca75..564893ec4edf 100644
--- a/sympy/core/tests/test_evalf.py
+++ b/sympy/core/tests/test_evalf.py
@@ -579,3 +579,13 @@ def test_issue_13425():
 
 def test_issue_17421():
     assert N(acos(-I + acosh(cosh(cosh(1) + I)))) == 1.0*I
+
+
+def test_issue_20291():
+    from sympy import FiniteSet
+    a = Symbol('a')
+    b = Symbol('b')
+    A = FiniteSet(a, b)
+    assert A.evalf(subs={a: 1, b: 2}) == FiniteSet(1, 2)
+    B = FiniteSet(a-b, 1)
+    assert B.evalf(subs={a: 1, b: 2}) == FiniteSet(-1, 1)


Code review comments:
diff_hunk: @@ -1473,7 +1473,11 @@ def evalf(self, n=15, subs=None, maxn=100, chop=False, strict=False, quad=None,
             result = evalf(self, prec + 4, options)
         except NotImplementedError:
             # Fall back to the ordinary evalf
-            v = self._eval_evalf(prec)
+            from sympy.core.basic import Basic
+            if isinstance(self, Basic) and subs is not None:  # issue 20291
path: sympy/core/evalf.py
text: @user1:
What is the purpose of this change? Won't `self` always be `Basic`?

@author:
`EvalfMixin` is not an instance of `Basic`. I think all its children are instances of `Basic` but I am not 100% sure. That's why I added it in. Should I remove the `Basic` check?