Repository: sympy/sympy
Pull request: Solvers: ValueError when linear_coeffs is used on pure sp.numbers.Float expressions
Fixes #17774 

> In [19]: import sympy as sp
    ...: from sympy.solvers.solveset import linear_coeffs
    ...: syms = sp.symbols('x y')
    ...: x, y = syms
In [20]: linear_coeffs(1, *syms)  # works as expected
Out[20]: [0, 0, 1]
In [21]: linear_coeffs(sp.numbers.One(), *syms)  # works as expected
Out[21]: [0, 0, 1]
In [22]: linear_coeffs(sp.numbers.Integer(1), *syms)  # works as expected
Out[22]: [0, 0, 1]
In [23]: linear_coeffs(sp.numbers.Float(1.0), *syms)  **# throws ValueError**
Traceback (most recent call last):
**ValueError: nonlinear term encountered: 1.00000000000000**

After the change:
>> In [19]: import sympy as sp
    ...: from sympy.solvers.solveset import linear_coeffs
    ...: syms = sp.symbols('x y')
    ...: x, y = syms
In [20]: linear_coeffs(1, *syms)  # works as expected
Out[20]: [0, 0, 1]
In [21]: linear_coeffs(sp.numbers.One(), *syms)  # works as expected
Out[21]: [0, 0, 1]
In [22]: linear_coeffs(sp.numbers.Integer(1), *syms)  # works as expected
Out[22]: [0, 0, 1]
In [23]: linear_coeffs(sp.numbers.Float(1.0), *syms)  **# works as expected**
**Out[23]: [0, 0, 1.0]**



<!-- BEGIN RELEASE NOTES -->
* solvers
  * linear_coeffs gives no longer raises when expr has no symbols of interest

<!-- END RELEASE NOTES -->


Problem statement:
linear_coeffs fails for pure sp.numbers.Float expressions
The `sympy.solvers.solveset.linear_coeffs` method (and therefore also the `linear_eq_to_matrix` method) throws an unjustified error if the input expression `eq` is a constant numerical value of type `sympy.numbers.Float`. For other built-in numerical types (and implicit conversion of integers) this works as expected:

```python
In [19]: import sympy as sp
    ...: from sympy.solvers.solveset import linear_coeffs
    ...: syms = sp.symbols('x y')
    ...: x, y = syms
In [20]: linear_coeffs(1, *syms)  # works as expected
Out[20]: [0, 0, 1]
In [21]: linear_coeffs(sp.numbers.One(), *syms)  # works as expected
Out[21]: [0, 0, 1]
In [22]: linear_coeffs(sp.numbers.Integer(1), *syms)  # works as expected
Out[22]: [0, 0, 1]
In [23]: linear_coeffs(sp.numbers.Float(1.0), *syms)  # throws ValueError
Traceback (most recent call last):
ValueError: nonlinear term encountered: 1.00000000000000
```

If the expression has a variable dependency, it works as well:
```python
In [24]: linear_coeffs(x * sp.numbers.Float(1.0), *syms)  # works as expected
Out[24]: [1.00000000000000, 0, 0]
```

I traced this back to the `core.numbers.as_coeff_add` method, which contains a `self.is_Rational` condition. Ultimately this keeps the `Float` in the `linear_coeffs` local `terms` variable, which makes the expression look nonlinear.
(if the expression is not a pure number, the `core.expr.Expr.as_coeff_add` method is used, which works as expected).
 
I understand that the `self.is_Rational` condition is probably necessary for other applications, such as simplifications. What would be a good fix for this? An early check for constant expressions in  `linear_coeffs`?

Hints from issue discussion:
Perhaps
```python
    eq = _sympify(eq)
    if not eq.has(*syms):
        return [S.Zero]*len(syms) + [eq]
    c, terms = eq.as_coeff_add(*syms)
```
tested with
```python
>>> linear_coeffs(1.,x)
[0, 1.00000000000000]
>>> linear_coeffs(1.)
[1.00000000000000]
```
Thanks @smichr, that does the job. I was patching this as 
```python
    if isinstance(eq, sp.numbers.Float):
        c, terms = eq, tuple()
    else:
        c, terms = _sympify(eq).as_coeff_add(*syms)
```
but returning early is of course preferable.

Commit message: Added a testcase

Patch to review:
diff --git a/sympy/solvers/solveset.py b/sympy/solvers/solveset.py
index 5ad6b3b248c7..7b307b1e45a6 100644
--- a/sympy/solvers/solveset.py
+++ b/sympy/solvers/solveset.py
@@ -2193,7 +2193,10 @@ def linear_coeffs(eq, *syms, **_kw):
     ValueError: nonlinear term encountered: x*(y + 1)
     """
     d = defaultdict(list)
-    c, terms = _sympify(eq).as_coeff_add(*syms)
+    eq = _sympify(eq)
+    if not eq.has(*syms):
+        return [S.Zero]*len(syms) + [eq]
+    c, terms = eq.as_coeff_add(*syms)
     d[0].extend(Add.make_args(c))
     for t in terms:
         m, f = t.as_coeff_mul(*syms)
diff --git a/sympy/solvers/tests/test_solveset.py b/sympy/solvers/tests/test_solveset.py
index a66c9454e97b..b3e8fe1e2043 100644
--- a/sympy/solvers/tests/test_solveset.py
+++ b/sympy/solvers/tests/test_solveset.py
@@ -2081,7 +2081,10 @@ def test_solve_logarithm():
 
 
 def test_linear_coeffs():
+    import sympy as sp
     from sympy.solvers.solveset import linear_coeffs
+    syms = sp.symbols('x y')
+    x, y = syms
     assert linear_coeffs(0, x) == [0, 0]
     assert all(i is S.Zero for i in linear_coeffs(0, x))
     assert linear_coeffs(x + 2*y + 3, x, y) == [1, 2, 3]
@@ -2092,6 +2095,7 @@ def test_linear_coeffs():
     raises(ValueError, lambda:
         linear_coeffs(1/x*(x - 1) + 1/x, x))
     assert linear_coeffs(a*(x + y), x, y) == [a, a, 0]
+    assert linear_coeffs(sp.numbers.Float(1.0), *syms) == [0, 0, 1.0]
 
 # modular tests
 def test_is_modular():


Code review comments:
diff_hunk: @@ -2092,6 +2095,7 @@ def test_linear_coeffs():
     raises(ValueError, lambda:
         linear_coeffs(1/x*(x - 1) + 1/x, x))
     assert linear_coeffs(a*(x + y), x, y) == [a, a, 0]
+    assert linear_coeffs(sp.numbers.Float(1.0), *syms) == [0, 0, 1.0]
path: sympy/solvers/tests/test_solveset.py
text: @user1:
You can just use 1.0 rather than `sp.numbers.Float(1.0)`

@user2:
And `x` and `y` are defined at the top of the file so the only line needed is `assert linear_coeffs(1.0, x, y) == [0, 0, 1.0]`

@author:
Done the necessary changes.