Repository: sympy/sympy

Issue:
Error solving equation with zero as float
### Hello scipy team,
I found a weird thing while solving equations using latest sympy module, using the solver with `0` it returns the right value but using with the float of `0` → `0.0` it returns an error.

![Sympy Float Error](https://quentium.fr/+Files/stackfiles/sympy_float.png)

Python version: `3.6.8`
Sympy version: `1.5.1`

Discussion:
sympy-bot: :white_check_mark:

Hi, I am the [SymPy bot](https://github.com/sympy/sympy-bot) (v158). I'm here to help you write a release notes entry. Please read the [guide on how to write release notes](https://github.com/sympy/sympy/wiki/Writing-Release-Notes).



Your release notes are in good order.

Here is what the release notes will look like:
* solvers
  * no longer interpret 0./1. as bool when the other arg is not a Boolean ([#19054](https://github.com/sympy/sympy/pull/19054) by [@smichr](https://github.com/smichr))

This will be added to https://github.com/sympy/sympy/wiki/Release-Notes-for-1.6.

Note: This comment will be updated with the latest check if you edit the pull request. You need to reload the page to see it. <details><summary>Click here to see the pull request description that was parsed.</summary>

    When testing whether x is in (S.true, S.false) one
    must be careful to consider whether 0 and 1 should
    pass this test because that is the Python default.

    SymPy can test more specifically, however, whether
    x is a literal True or False. That is now being done
    where failure to do so was previously causing an error.

    <!-- 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. -->

    closes #19048

    #### Brief description of what is fixed or changed


    #### 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 -->
    * solvers
        * no longer interpret 0./1. as bool when the other arg is not a Boolean
    <!-- END RELEASE NOTES -->

</details><p>

codecov[bot]: # [Codecov](https://codecov.io/gh/sympy/sympy/pull/19054?src=pr&el=h1) Report
> Merging [#19054](https://codecov.io/gh/sympy/sympy/pull/19054?src=pr&el=desc) into [master](https://codecov.io/gh/sympy/sympy/commit/b6fdf957407bb73184e3117c47e857e46f06f9d4&el=desc) will **increase** coverage by `0.010%`.
> The diff coverage is `85.714%`.

```diff
@@              Coverage Diff              @@
##            master    #19054       +/-   ##
=============================================
+ Coverage   75.766%   75.776%   +0.010%     
=============================================
  Files          650       650               
  Lines       168908    168916        +8     
  Branches     39816     39821        +5     
=============================================
+ Hits        127975    127999       +24     
+ Misses       35360     35343       -17     
- Partials      5573      5574        +1     
```

smichr: Should Eq(True, 1) be False (master) or True (this PR)? It would not be hard (per commit note) to limit sanitization to cases of non-trivial Boolean only.

Patch:
diff --git a/sympy/core/compatibility.py b/sympy/core/compatibility.py
index e57d25fdbb..c01f10337e 100644
--- a/sympy/core/compatibility.py
+++ b/sympy/core/compatibility.py
@@ -369,6 +369,8 @@ def as_int(n, strict=True):
     """
     if strict:
         try:
+            if type(n) is bool:
+                raise TypeError
             return operator.index(n)
         except TypeError:
             raise ValueError('%s is not an integer' % (n,))
diff --git a/sympy/core/numbers.py b/sympy/core/numbers.py
index 32fbfa4b5a..7e42831ca4 100644
--- a/sympy/core/numbers.py
+++ b/sympy/core/numbers.py
@@ -1389,12 +1389,15 @@ def __int__(self):
     __long__ = __int__
 
     def __eq__(self, other):
+        from sympy.logic.boolalg import Boolean
         try:
             other = _sympify(other)
         except SympifyError:
             return NotImplemented
         if not self:
             return not other
+        if isinstance(other, Boolean):
+            return False
         if other.is_NumberSymbol:
             if other.is_irrational:
                 return False
diff --git a/sympy/core/relational.py b/sympy/core/relational.py
index 8fee305559..4a5a24b2f6 100644
--- a/sympy/core/relational.py
+++ b/sympy/core/relational.py
@@ -4,7 +4,7 @@
 
 from sympy.utilities.exceptions import SymPyDeprecationWarning
 from .add import _unevaluated_Add, Add
-from .basic import S
+from .basic import S, Atom
 from .compatibility import ordered
 from .basic import Basic
 from .expr import Expr
@@ -21,6 +21,10 @@
 )
 
 
+def _nontrivBool(side):
+    return isinstance(side, Boolean) and \
+        not isinstance(side, (BooleanAtom, Atom))
+
 
 # Note, see issue 4986.  Ideally, we wouldn't want to subclass both Boolean
 # and Expr.
@@ -78,27 +82,18 @@ def __new__(cls, lhs, rhs, rop=None, **assumptions):
         if cls is None:
             raise ValueError("Invalid relational operator symbol: %r" % rop)
 
-        # XXX: Why should the below be removed when Py2 is not supported?
-        #
-        # /// drop when Py2 is no longer supported
         if not issubclass(cls, (Eq, Ne)):
             # validate that Booleans are not being used in a relational
             # other than Eq/Ne;
             # Note: Symbol is a subclass of Boolean but is considered
             # acceptable here.
-            from sympy.core.symbol import Symbol
-            from sympy.logic.boolalg import Boolean
-            def unacceptable(side):
-                return isinstance(side, Boolean) and not isinstance(side, Symbol)
-
-            if unacceptable(lhs) or unacceptable(rhs):
+            if any(map(_nontrivBool, (lhs, rhs))):
                 from sympy.utilities.misc import filldedent
                 raise TypeError(filldedent('''
                     A Boolean argument can only be used in
                     Eq and Ne; all other relationals expect
                     real expressions.
                 '''))
-        # \\\
 
         return cls(lhs, rhs, **assumptions)
 
@@ -455,6 +450,12 @@ class Equality(Relational):
     Notes
     =====
 
+    Python treats 1 and True (and 0 and False) as being equal; SymPy
+    does not. And integer will always compare as unequal to a Boolean:
+
+    >>> Eq(True, 1), True == 1
+    (False, True)
+
     This class is not the same as the == operator.  The == operator tests
     for exact structural equality between two expressions; this class
     compares expressions mathematically.
@@ -498,6 +499,10 @@ def __new__(cls, lhs, rhs=None, **options):
         evaluate = options.pop('evaluate', global_parameters.evaluate)
 
         if evaluate:
+            if isinstance(lhs, Boolean) != isinstance(lhs, Boolean):
+                # e.g. 0/1 not recognized as Boolean in SymPy
+                return S.false
+
             # If one expression has an _eval_Eq, return its results.
             if hasattr(lhs, '_eval_Eq'):
                 r = lhs._eval_Eq(rhs)
@@ -728,6 +733,10 @@ def __new__(cls, lhs, rhs, **options):
         evaluate = options.pop('evaluate', global_parameters.evaluate)
 
         if evaluate:
+            if isinstance(lhs, Boolean) != isinstance(lhs, Boolean):
+                # e.g. 0/1 not recognized as Boolean in SymPy
+                return S.true
+
             is_equal = Equality(lhs, rhs)
             if isinstance(is_equal, BooleanAtom):
                 return is_equal.negated
diff --git a/sympy/logic/boolalg.py b/sympy/logic/boolalg.py
index 03b0c8283d..35bd4e4ac6 100644
--- a/sympy/logic/boolalg.py
+++ b/sympy/logic/boolalg.py
@@ -436,7 +436,6 @@ def simplify(self, **kwargs):
         from sympy.simplify.simplify import simplify
         return simplify(self, **kwargs)
 
-    # /// drop when Py2 is no longer supported
     def __lt__(self, other):
         from sympy.utilities.misc import filldedent
         raise TypeError(filldedent('''
@@ -447,7 +446,6 @@ def __lt__(self, other):
     __le__ = __lt__
     __ge__ = __lt__
     __gt__ = __lt__
-    # \\\
 
     @classmethod
     def binary_check_and_simplify(self, *args):
diff --git a/sympy/solvers/solvers.py b/sympy/solvers/solvers.py
index ecc408c22f..90486eedac 100644
--- a/sympy/solvers/solvers.py
+++ b/sympy/solvers/solvers.py
@@ -262,9 +262,9 @@ def checksol(f, symbol, sol=None, **flags):
         if f.rhs in (S.true, S.false):
             f = f.reversed
         B, E = f.args
-        if B in (S.true, S.false):
+        if isinstance(B, BooleanAtom):
             f = f.subs(sol)
-            if f not in (S.true, S.false):
+            if not f.is_Boolean:
                 return
         else:
             f = f.rewrite(Add, evaluate=False)
@@ -364,8 +364,6 @@ def checksol(f, symbol, sol=None, **flags):
         elif val.is_Rational:
             return val == 0
         if numerical and val.is_number:
-            if val in (S.true, S.false):
-                return bool(val)
             return (abs(val.n(18).n(12, chop=True)) < 1e-9) is S.true
         was = val
 
@@ -977,11 +975,10 @@ def _sympified_list(w):
             if 'ImmutableDenseMatrix' in [type(a).__name__ for a in fi.args]:
                 fi = fi.lhs - fi.rhs
             else:
-                args = fi.args
-                if args[1] in (S.true, S.false):
-                    args = args[1], args[0]
-                L, R = args
-                if L in (S.false, S.true):
+                L, R = fi.args
+                if isinstance(R, BooleanAtom):
+                    L, R = R, L
+                if isinstance(L, BooleanAtom):
                     if isinstance(fi, Unequality):
                         L = ~L
                     if R.is_Relational:


Test patch:
diff --git a/sympy/core/tests/test_compatibility.py b/sympy/core/tests/test_compatibility.py
index af3bddba10..6a53368540 100644
--- a/sympy/core/tests/test_compatibility.py
+++ b/sympy/core/tests/test_compatibility.py
@@ -12,6 +12,7 @@ def test_default_sort_key():
 
 
 def test_as_int():
+    raises(ValueError, lambda : as_int(True))
     raises(ValueError, lambda : as_int(1.1))
     raises(ValueError, lambda : as_int([]))
     raises(ValueError, lambda : as_int(S.NaN))
@@ -25,6 +26,7 @@ def test_as_int():
     # integer. This is not -- by design -- as_ints role.
     raises(ValueError, lambda : as_int(1e23))
     raises(ValueError, lambda : as_int(S('1.'+'0'*20+'1')))
+    assert as_int(True, strict=False) == 1
 
 
 def test_iterable():
diff --git a/sympy/core/tests/test_relational.py b/sympy/core/tests/test_relational.py
index 003f56b44c..e543416972 100644
--- a/sympy/core/tests/test_relational.py
+++ b/sympy/core/tests/test_relational.py
@@ -114,7 +114,7 @@ def test_wrappers():
     assert Ne(y, x + x**2) == res
 
 
-def test_Eq():
+def test_Eq_Ne():
 
     assert Eq(x, x)  # issue 5719
 
@@ -125,10 +125,20 @@ def test_Eq():
     p = Symbol('p', positive=True)
     assert Eq(p, 0) is S.false
 
-    # issue 13348
+    # issue 13348; 19048
+    # SymPy is strict about 0 and 1 not being
+    # interpreted as Booleans
     assert Eq(True, 1) is S.false
+    assert Eq(False, 0) is S.false
+    assert Eq(~x, 0) is S.false
+    assert Eq(~x, 1) is S.false
+    assert Ne(True, 1) is S.true
+    assert Ne(False, 0) is S.true
+    assert Ne(~x, 0) is S.true
+    assert Ne(~x, 1) is S.true
 
     assert Eq((), 1) is S.false
+    assert Ne((), 1) is S.true
 
 
 def test_as_poly():
@@ -979,10 +989,9 @@ def test_binary_symbols():
 
 
 def test_rel_args():
-    # can't have Boolean args; this is automatic with Python 3
-    # so this test and the __lt__, etc..., definitions in
-    # relational.py and boolalg.py which are marked with ///
-    # can be removed.
+    # can't have Boolean args; this is automatic for True/False
+    # with Python 3 and we confirm that SymPy does the same
+    # for true/false
     for op in ['<', '<=', '>', '>=']:
         for b in (S.true, x < 1, And(x, y)):
             for v in (0.1, 1, 2**32, t, S.One):
diff --git a/sympy/logic/tests/test_boolalg.py b/sympy/logic/tests/test_boolalg.py
index 277564c8ac..43dd106288 100644
--- a/sympy/logic/tests/test_boolalg.py
+++ b/sympy/logic/tests/test_boolalg.py
@@ -383,11 +383,21 @@ def test_bool_symbol():
 
 
 def test_is_boolean():
-    assert true.is_Boolean
+    assert isinstance(True, Boolean) is False
+    assert isinstance(true, Boolean) is True
+    assert 1 == True
+    assert 1 != true
+    assert (1 == true) is False
+    assert 0 == False
+    assert 0 != false
+    assert (0 == false) is False
+    assert true.is_Boolean is True
     assert (A & B).is_Boolean
     assert (A | B).is_Boolean
     assert (~A).is_Boolean
     assert (A ^ B).is_Boolean
+    assert A.is_Boolean != isinstance(A, Boolean)
+    assert isinstance(A, Boolean)
 
 
 def test_subs():
diff --git a/sympy/solvers/tests/test_solvers.py b/sympy/solvers/tests/test_solvers.py
index 1df79d839f..88bd8d0cb1 100644
--- a/sympy/solvers/tests/test_solvers.py
+++ b/sympy/solvers/tests/test_solvers.py
@@ -165,13 +165,15 @@ def test_solve_args():
     # - linear
     assert solve((x + y - 2, 2*x + 2*y - 4)) == {x: -y + 2}
     # When one or more args are Boolean
+    assert solve(Eq(x**2, 0.0)) == [0]  # issue 19048
     assert solve([True, Eq(x, 0)], [x], dict=True) == [{x: 0}]
     assert solve([Eq(x, x), Eq(x, 0), Eq(x, x+1)], [x], dict=True) == []
     assert not solve([Eq(x, x+1), x < 2], x)
     assert solve([Eq(x, 0), x+1<2]) == Eq(x, 0)
     assert solve([Eq(x, x), Eq(x, x+1)], x) == []
     assert solve(True, x) == []
-    assert solve([x-1, False], [x], set=True) == ([], set())
+    assert solve([x - 1, False], [x], set=True) == ([], set())
+
 
 def test_solve_polynomial1():
     assert solve(3*x - 2, x) == [Rational(2, 3)]
@@ -605,10 +607,13 @@ def test_solve_inequalities():
     assert solve(Eq(x < 1, True)) == (-oo < x) & (x < 1)
 
     assert solve(Eq(False, x)) == False
+    assert solve(Eq(0, x)) == [0]
     assert solve(Eq(True, x)) == True
+    assert solve(Eq(1, x)) == [1]
     assert solve(Eq(False, ~x)) == True
     assert solve(Eq(True, ~x)) == False
     assert solve(Ne(True, x)) == False
+    assert solve(Ne(1, x)) == (x > -oo) & (x < oo) & Ne(x, 1)
 
 
 def test_issue_4793():