Repository: diofant/diofant

Issue:
dup_count_real_roots() is broken for composite domains
This is wrong:
```
In [16]: R1 = ZZ.poly_ring(y).poly_ring(x)

In [17]: R1.dup_count_real_roots(R1.from_expr(x**7 + y*x + 1))
Out[17]: 1
```
That's easy to demonstrate even for real `y`:
```
In [18]: R2 = ZZ.poly_ring(x)

In [19]: R2.dup_count_real_roots(R2.from_expr(x**7 + 2*x + 1))
Out[19]: 1

In [20]: R2.dup_count_real_roots(R2.from_expr(x**7 - 2*x + 1))
Out[20]: 3
```

Same issue is valid for SymPy:
```
In [1]: Poly(x**7 + x*y + 1, x)
Out[1]: Poly(x**7 + y*x + 1, x, domain='ZZ[y]')

In [2]: _.count_roots()
Out[2]: 1

In [3]: Poly(x**7 + x*2 + 1, x).count_roots()
Out[3]: 1

In [4]: Poly(x**7 - x*2 + 1, x).count_roots()
Out[4]: 3
```

Simple solution: add a quick exit for Composite domains.  Probably, raising `DomainError` would be fine.


Discussion:
@jksuom, you may be interested in, as issue is clearly valid for sympy too.  Simple fix: #741 (unfortunately, will require some substantial changes in rationaltools module).
DomainError should probably be raised also for many non-composite domains. Sturm's theorem only works on (real closures of) ordered fields.

Resolving patch:
diff --git a/diofant/polys/rootisolation.py b/diofant/polys/rootisolation.py
--- a/diofant/polys/rootisolation.py
+++ b/diofant/polys/rootisolation.py
@@ -418,9 +418,12 @@ def dup_isolate_real_roots_sqf(f, K, eps=None, inf=None, sup=None, fast=False, b
         R, K = K, K.field
         f = dmp_convert(f, 0, R, K)
 
+    if not (K.is_ComplexAlgebraicField or K.is_RationalField):
+        raise DomainError("Can't isolate real roots in domain %s" % K)
+
     f = dmp_clear_denoms(f, 0, K)[1]
 
-    if K.is_AlgebraicField and not K.is_RealAlgebraicField:
+    if K.is_ComplexAlgebraicField and not K.is_RealAlgebraicField:
         A, K = K, K.domain
         polys = [dmp_eval_in(_, K.zero, 1, 1, K) for _ in dup_real_imag(f, A)]
         if not polys[1]:
@@ -430,9 +433,6 @@ def dup_isolate_real_roots_sqf(f, K, eps=None, inf=None, sup=None, fast=False, b
             roots = [_[0] for _ in roots if _[1].keys() == {0, 1}]
             return [RealInterval((a, b), f, K) for (a, b) in roots] if blackbox else roots
 
-    if not (K.is_RationalField or K.is_RealAlgebraicField):
-        raise DomainError("isolation of real roots not supported over %s" % K)
-
     if dmp_degree_in(f, 0, 0) <= 0:
         return []
 
@@ -614,6 +614,9 @@ def dup_count_real_roots(f, K, inf=None, sup=None):
         R, K = K, K.field
         f = dmp_convert(f, 0, R, K)
 
+    if not (K.is_ComplexAlgebraicField or K.is_RationalField):
+        raise DomainError("Can't count real roots in domain %s" % K)
+
     if K.is_AlgebraicField:
         return sum(k for *_, k in dup_isolate_real_roots(f, K, inf, sup))
 
@@ -1158,6 +1161,9 @@ def dup_count_complex_roots(f, K, inf=None, sup=None, exclude=None):
         R, K = K, K.field
         f = dmp_convert(f, 0, R, K)
 
+    if not (K.is_ComplexAlgebraicField or K.is_RationalField):
+        raise DomainError("Can't count complex roots in domain %s" % K)
+
     if not all(isinstance(_, tuple) for _ in (inf, sup)):
         B = _roots_bound(f, K)
 
@@ -1177,7 +1183,7 @@ def dup_count_complex_roots(f, K, inf=None, sup=None, exclude=None):
 
     f1, f2 = dup_real_imag(f, K)
 
-    if K.is_ComplexAlgebraicField and not K.is_RealAlgebraicField:
+    if not (K.is_RationalField or K.is_RealAlgebraicField):
         K = K.domain
 
     return _count_roots(f1, f2, K, (u, v), (s, t), exclude)
@@ -1429,6 +1435,9 @@ def dup_isolate_complex_roots_sqf(f, K, eps=None, inf=None, sup=None, blackbox=F
         R, K = K, K.field
         f = dmp_convert(f, 0, R, K)
 
+    if not (K.is_ComplexAlgebraicField or K.is_RationalField):
+        raise DomainError("Can't isolate complex roots in domain %s" % K)
+
     if not all(isinstance(_, tuple) for _ in (inf, sup)):
         B = _roots_bound(f, K)
 
@@ -1460,7 +1469,7 @@ def dup_isolate_complex_roots_sqf(f, K, eps=None, inf=None, sup=None, blackbox=F
                     _roots.append(croot)
                 _roots.append(root)
             roots = _roots
-        elif K.is_ComplexAlgebraicField:
+        else:
             # Take conjugated polynomial to get solutions in the
             # bottom half-plane.
             f = [_.conjugate() for _ in f]
@@ -1469,14 +1478,12 @@ def dup_isolate_complex_roots_sqf(f, K, eps=None, inf=None, sup=None, blackbox=F
                                                              inf=(u, 0), sup=(s, -v),
                                                              blackbox=True)]
             roots = sorted(roots, key=lambda r: (r.ax, r.ay))
-        else:
-            raise NotImplementedError
 
         return roots if blackbox else [r.as_tuple() for r in roots]
 
     f1, f2 = dup_real_imag(f, K)
 
-    if K.is_ComplexAlgebraicField and not K.is_RealAlgebraicField:
+    if not (K.is_RationalField or K.is_RealAlgebraicField):
         K = K.domain
 
     f1L1 = dmp_eval_in(f1, v, 1, 1, K)


Test patch:
diff --git a/diofant/polys/tests/test_rootisolation.py b/diofant/polys/tests/test_rootisolation.py
--- a/diofant/polys/tests/test_rootisolation.py
+++ b/diofant/polys/tests/test_rootisolation.py
@@ -929,6 +929,9 @@ def test_dup_isolate_complex_roots_sqf():
                                                   ((0, QQ(-283, 100)), (QQ(283, 100), 0)),
                                                   ((0, 0), (QQ(283, 100), QQ(283, 100)))]
 
+    R, x = ring("x", EX)
+    pytest.raises(DomainError, lambda: R.dup_isolate_complex_roots_sqf(x))
+
 
 def test_dup_isolate_all_roots_sqf():
     R, x = ring("x", ZZ)
@@ -991,3 +994,9 @@ def test_ComplexInterval():
         r1, r2 = r1.refine(), r2.refine()
 
     assert r1.is_disjoint(r2, check_re_refinement=True) is True
+
+
+def test_diofantissue_745():
+    D, y = ring('y', ZZ)
+    R, x = ring('x', D)
+    pytest.raises(DomainError, lambda: R.dup_count_real_roots(x**7 + y*x + 1))