Repository: diofant/diofant

Issue:
Inconsistency quo vs quo_ground in dense and sparse representations
Here is an example:
```python
In [1]: e = 2*x + 3                                                                                                                     

In [2]: dense = e.as_poly()                                                                                                             

In [3]: dense.quo(2, auto=False)                                                                                                        
Out[3]: Poly(x, x, domain='ZZ')

In [4]: dense.quo_ground(2)                                                                                                             
Out[4]: Poly(x + 1, x, domain='ZZ')

In [5]: R = ZZ.poly_ring(x)                                                                                                             

In [6]: sparse = R.from_expr(e)                                                                                                         

In [7]: sparse.quo(R.from_expr(2))                                                                                                      
Out[7]: x

In [8]: sparse.quo_ground(2)                                                                                                            
Out[8]: x
```

For sparse representation ``quo()`` and ``quo_ground()`` outputs are equal, but for dense (which currently uses Poly class) - are not.

@jksuom, what do you think (this issue is valid for sympy as well, if you replace from_expr -> from_sympy)?



Discussion:
I have regarded `quo_ground` as a low level method. It seems to be used internally when the division is expected to be exact, so the result is unambiguous. Actually, I had never noticed that it was made available as a high level method. It could probably be removed and replaced by the standard `quo` on the high level interface.
> result is unambiguous

There are two different results.  One for sparse and one for dense representations.

If the division is not exact (i.e., the divisor is not a factor of the dividend in the domain in question), then there is no general principle for determining the result. I would remove the high level methods `quo_ground` for both sparse and dense polynomials and use `quo` instead. I think that it makes sense to use the low level method only when it is known that the division is exact.
> ... there is no general principle for determining the result

Anyway, it seems that both quo_ground's methods should produce same answer.  I would pick up the sparse answer, as it seems consistent with the quo answer.

> I would remove the high level methods quo_ground for both sparse and dense polynomials and use quo instead.

Both sparse and dense quo_ground methods are used a lot.  Not sure if it's intentional or it's assumed that it's same as quo, but for constant polynomial.

> Both sparse and dense quo_ground methods are used a lot.

I have not looked at all uses but most of them seem ok. It seems that `quo_ground` on sparse polynomials is not correct for `monic`.
```
>>> R, x = ring('x', ZZ)
>>> (2*x + 1).monic()
x
```
Hmm, looks so.  Error should be raised, do you think?  Like
```
In [1]: R, x = ring('x', ZZ)                                                                                                            
In [2]: R.dmp_ground_monic(2*x + 1)
...
ExactQuotientFailed: 2 does not divide 1 in ZZ
```

BTW, difference dense vs sparse was traced to 2445aa04a from https://github.com/sympy/sympy/pull/1840.  Commit message a bit cryptic, it's not obvious why this "fix" require a different convention for sparse's quo_ground (which just drop a coefficient, if the division is not exact).
The sparse version of `quo_ground()` seems to have been introduced in https://github.com/sympy/sympy/pull/1840/commits/61874dc20bf9be42b091298d04d6a8ddcda75ef3. I think it was an error not to check for exact division.
> I think it was an error not to check for exact division.

I think, this was reserved for exquo_ground method (which might be useful for "//" with the right op from the ground domain).

Currently, "/" == "//" for PolyElement.  There is a todo line for ``__floordiv__()`` magic method to be for exquo().  In fact, probably "//" - should be for the current ``quo()`` method behaviour and ``/`` - reserved for ``exquo()``.

> probably "//" - should be for the current quo() method behaviour

I think I could agree with this (assuming that `quo()` would always return a result in the same ring). I am not quite sure about the relationship between `exquo()` and `/`.
Ok, I will try the following practical plan (which goes to https://github.com/diofant/diofant/pull/720) to fix this issue:
1. Remove usage of quo_ground() outside of polyclasses.py, polytools.py and rings.py (where DMP, Poly and PolyElement are defined)
2. Make PolyElement's quo_ground() be like DMP.quo_ground (== dmp_quo_ground()).
3. Implement PolyElement.exquo_ground() and use this in PolyElement.monic() (to raise ExactQuotientFailed in [your example](https://github.com/diofant/diofant/issues/702#issuecomment-433619312))
4. Don't implement ``/`` on PolyElement's.

Let me know, if you have some thoughts about this proposal.

I also believe, there may be something wrong in default coercion rules of PolyElement.  An example (on the PR #720's branch):
```
In [1]: R, x = ring('x', ZZ)                                                                                                            
In [2]: e = 2*x + 3                                                                                                                     
In [3]: e//2                                                                                                                            
Out[3]: x + 1
In [4]: e//R.from_expr(2)                                                                                                               
Out[4]: x
```
The reason is that in In[3] computation was dispatched to ``quo_ground()`` method and in In[4] - to ``quo()`` method.  However, same happens for the dense representation (DMP class).  I'm planning to use sparse representation instead of DMP for Poly's (see https://github.com/diofant/diofant/issues/711), so - mentioned issue (if it is an issue at all) may be fixed after the PolyElement will became a workhorse for Poly.


Resolving patch:
diff --git a/diofant/polys/ring_series.py b/diofant/polys/ring_series.py
--- a/diofant/polys/ring_series.py
+++ b/diofant/polys/ring_series.py
@@ -534,7 +534,7 @@ def rs_compose_add(p1, p2):
     np2e = rs_hadamard_exp(np2)
     np3e = rs_mul(np1e, np2e, x, prec)
     np3 = rs_hadamard_exp(np3e, True)
-    np3a = (np3[(0,)] - np3)/x
+    np3a = (np3[(0,)] - np3)//x
     q = rs_integrate(np3a, x)
     q = rs_exp(q, x, prec)
     q = _invert_monoms(q)
diff --git a/diofant/polys/rings.py b/diofant/polys/rings.py
--- a/diofant/polys/rings.py
+++ b/diofant/polys/rings.py
@@ -1190,7 +1190,7 @@ def __mod__(self, other):
     def __rmod__(self, other):
         return NotImplemented
 
-    def __truediv__(self, other):
+    def __floordiv__(self, other):
         ring = self.ring
 
         if not other:
@@ -1201,7 +1201,7 @@ def __truediv__(self, other):
             if isinstance(ring.domain, PolynomialRing) and ring.domain.ring == other.ring:
                 pass
             elif isinstance(other.ring.domain, PolynomialRing) and other.ring.domain.ring == ring:
-                return other.__rtruediv__(self)
+                return other.__rfloordiv__(self)
             else:
                 return NotImplemented
 
@@ -1212,13 +1212,26 @@ def __truediv__(self, other):
         else:
             return self.quo_ground(other)
 
-    def __rtruediv__(self, other):
+    def __rfloordiv__(self, other):
         return NotImplemented
 
-    __floordiv__ = __truediv__
-    __rfloordiv__ = __rtruediv__
+    def __truediv__(self, other):
+        ring = self.ring
+
+        if not other:
+            raise ZeroDivisionError("polynomial division")
+        elif isinstance(other, ring.domain.dtype):
+            return self.quo_ground(other)
 
-    # TODO: use // (__floordiv__) for exquo()?
+        try:
+            other = ring.domain_new(other)
+        except CoercionFailed:
+            return NotImplemented
+        else:
+            return self.quo_ground(other)
+
+    def __rtruediv__(self, other):
+        return NotImplemented
 
     def _term_div(self):
         zm = self.ring.zero_monom
@@ -1722,7 +1735,7 @@ def monic(self):
         if not self:
             return self
         else:
-            return self.quo_ground(self.LC)
+            return self.exquo_ground(self.LC)
 
     def mul_ground(self, x):
         if not x:
@@ -1758,9 +1771,25 @@ def quo_ground(self, x):
             quo = domain.quo
             terms = [(monom, quo(coeff, x)) for monom, coeff in self.items()]
         else:
-            terms = [(monom, coeff//x) for monom, coeff in self.items() if not (coeff % x)]
+            terms = [(monom, coeff//x) for monom, coeff in self.items()]
 
-        return self.new(terms)
+        p = self.new(terms)
+        p.strip_zero()
+        return p
+
+    def exquo_ground(self, x):
+        domain = self.ring.domain
+
+        if not x:
+            raise ZeroDivisionError('polynomial division')
+        if not self or x == domain.one:
+            return self
+
+        terms = [(monom, domain.exquo(coeff, x)) for monom, coeff in self.items()]
+
+        p = self.new(terms)
+        p.strip_zero()
+        return p
 
     def quo_term(self, term):
         monom, coeff = term


Test patch:
diff --git a/diofant/domains/tests/test_polynomialring.py b/diofant/domains/tests/test_polynomialring.py
--- a/diofant/domains/tests/test_polynomialring.py
+++ b/diofant/domains/tests/test_polynomialring.py
@@ -37,7 +37,7 @@ def test_globalring():
     assert X + Y == R.convert(x + y)
     assert X - Y == R.convert(x - y)
     assert X + 1 == R.convert(x + 1)
-    assert X**2 / X == X
+    assert X**2 // X == X
 
     assert R.convert(ZZ.poly_ring(x, y).convert(x), ZZ.poly_ring(x, y)) == X
     assert R.convert(Qxy.convert(x), Qxy) == X
@@ -56,7 +56,7 @@ def test_localring():
     assert X + Y == R.convert(x + y)
     assert X - Y == R.convert(x - y)
     assert X + 1 == R.convert(x + 1)
-    assert X**2 / X == X
+    assert X**2 // X == X
 
     assert R.convert(ZZ.poly_ring(x, y).convert(x), ZZ.poly_ring(x, y)) == X
     assert R.convert(Qxy.convert(x), Qxy) == X
diff --git a/diofant/polys/tests/test_euclidtools.py b/diofant/polys/tests/test_euclidtools.py
--- a/diofant/polys/tests/test_euclidtools.py
+++ b/diofant/polys/tests/test_euclidtools.py
@@ -732,7 +732,7 @@ def test_dmp_primitive():
         g *= f
         F += x**i*g
 
-    assert R.dmp_primitive(F) == (f.drop(x), F / f)
+    assert R.dmp_primitive(F) == (f.drop(x), F // f)
 
     R,  x, y, z = ring("x,y,z", ZZ)
 
diff --git a/diofant/polys/tests/test_rings.py b/diofant/polys/tests/test_rings.py
--- a/diofant/polys/tests/test_rings.py
+++ b/diofant/polys/tests/test_rings.py
@@ -224,8 +224,8 @@ def test_PolyElement___eq__():
     Rt, t = ring("t", ZZ)
     R,  x, y = ring("x,y", Rt)
 
-    assert (t**3*x/x == t**3) is True
-    assert (t**3*x/x == t**4) is False
+    assert (t**3*x//x == t**3) is True
+    assert (t**3*x//x == t**4) is False
 
 
 def test_PolyElement__lt_le_gt_ge__():
@@ -629,17 +629,17 @@ def test_PolyElement___mul__():
     assert p == p2 and p2 == R.zero
 
 
-def test_PolyElement___truediv__():
+def test_PolyElement___floordiv__truediv__():
     R,  x, y, z = ring("x,y,z", ZZ)
 
     assert (2*x**2 - 4)/2 == x**2 - 2
-    assert (2*x**2 - 3)/2 == x**2
+    assert (2*x**2 - 3)/2 == x**2 - 2
 
-    assert (x**2 - 1)/x == (x**2 - 1).quo(x) == x
-    assert (x**2 - x)/x == (x**2 - x).quo(x) == x - 1
+    assert (x**2 - 1)//x == (x**2 - 1).quo(x) == x
+    assert (x**2 - x)//x == (x**2 - x).quo(x) == x - 1
 
-    assert (x**2 - 1)/(2*x) == (x**2 - 1).quo(2*x) == 0
-    assert (x**2 - x)/(x - 1) == (x**2 - x).quo(x - 1) == x
+    assert (x**2 - 1)//(2*x) == (x**2 - 1).quo(2*x) == 0
+    assert (x**2 - x)//(x - 1) == (x**2 - x).quo(x - 1) == x
 
     R,  x, y, z = ring("x,y,z", ZZ)
     assert len((x**2/3 + y**3/4 + z**4/5).terms()) == 0
@@ -647,17 +647,19 @@ def test_PolyElement___truediv__():
     R,  x, y, z = ring("x,y,z", QQ)
     assert len((x**2/3 + y**3/4 + z**4/5).terms()) == 3
 
+    pytest.raises(ZeroDivisionError, lambda: x/0)
+
     Rt, t = ring("t", ZZ)
     Ruv,  u, v = ring("u,v", ZZ)
     Rxyz,  x, y, z = ring("x,y,z", Ruv)
 
     assert dict((u**2*x + u)/u) == {(1, 0, 0): u, (0, 0, 0): 1}
-    pytest.raises(TypeError, lambda: u/(u**2*x + u))
+    pytest.raises(TypeError, lambda: u//(u**2*x + u))
 
-    pytest.raises(TypeError, lambda: t/x)
-    pytest.raises(TypeError, lambda: x/t)
-    pytest.raises(TypeError, lambda: t/u)
-    pytest.raises(TypeError, lambda: u/t)
+    pytest.raises(TypeError, lambda: t//x)
+    pytest.raises(TypeError, lambda: x//t)
+    pytest.raises(TypeError, lambda: t//u)
+    pytest.raises(TypeError, lambda: u//t)
 
     assert divmod(x, u) == (0, x)
     assert x % u == x
@@ -682,7 +684,7 @@ def test_PolyElement___truediv__():
     pytest.raises(ZeroDivisionError, lambda: f.rem(g))
     pytest.raises(ZeroDivisionError, lambda: f % g)
     pytest.raises(ZeroDivisionError, lambda: f.quo(g))
-    pytest.raises(ZeroDivisionError, lambda: f / g)
+    pytest.raises(ZeroDivisionError, lambda: f // g)
     pytest.raises(ZeroDivisionError, lambda: f.exquo(g))
 
     R,  x, y = ring("x,y", ZZ)
@@ -693,7 +695,7 @@ def test_PolyElement___truediv__():
     pytest.raises(ZeroDivisionError, lambda: f.rem(g))
     pytest.raises(ZeroDivisionError, lambda: f % g)
     pytest.raises(ZeroDivisionError, lambda: f.quo(g))
-    pytest.raises(ZeroDivisionError, lambda: f / g)
+    pytest.raises(ZeroDivisionError, lambda: f // g)
     pytest.raises(ZeroDivisionError, lambda: f.exquo(g))
 
     R, x = ring("x", ZZ)
@@ -703,7 +705,7 @@ def test_PolyElement___truediv__():
 
     assert f.div(g) == divmod(f, g) == (q, r)
     assert f.rem(g) == f % g == r
-    assert f.quo(g) == f / g == q
+    assert f.quo(g) == f // g == q
     pytest.raises(ExactQuotientFailed, lambda: f.exquo(g))
 
     assert R.zero.div(f) == (R.zero, R.zero)
@@ -714,7 +716,7 @@ def test_PolyElement___truediv__():
 
     assert f.div(g) == divmod(f, g) == (q, r)
     assert f.rem(g) == f % g == r
-    assert f.quo(g) == f / g == q
+    assert f.quo(g) == f // g == q
     pytest.raises(ExactQuotientFailed, lambda: f.exquo(g))
 
     f, g = 5*x**4 + 4*x**3 + 3*x**2 + 2*x + 1, x**2 + 2*x + 3
@@ -722,7 +724,7 @@ def test_PolyElement___truediv__():
 
     assert f.div(g) == divmod(f, g) == (q, r)
     assert f.rem(g) == f % g == r
-    assert f.quo(g) == f / g == q
+    assert f.quo(g) == f // g == q
     pytest.raises(ExactQuotientFailed, lambda: f.exquo(g))
 
     f, g = 5*x**5 + 4*x**4 + 3*x**3 + 2*x**2 + x, x**4 + 2*x**3 + 9
@@ -730,7 +732,7 @@ def test_PolyElement___truediv__():
 
     assert f.div(g) == divmod(f, g) == (q, r)
     assert f.rem(g) == f % g == r
-    assert f.quo(g) == f / g == q
+    assert f.quo(g) == f // g == q
     pytest.raises(ExactQuotientFailed, lambda: f.exquo(g))
 
     R, x = ring("x", QQ)
@@ -740,7 +742,7 @@ def test_PolyElement___truediv__():
 
     assert f.div(g) == divmod(f, g) == (q, r)
     assert f.rem(g) == f % g == r
-    assert f.quo(g) == f / g == q
+    assert f.quo(g) == f // g == q
     pytest.raises(ExactQuotientFailed, lambda: f.exquo(g))
 
     f, g = 3*x**3 + x**2 + x + 5, 5*x**2 - 3*x + 1
@@ -748,7 +750,7 @@ def test_PolyElement___truediv__():
 
     assert f.div(g) == divmod(f, g) == (q, r)
     assert f.rem(g) == f % g == r
-    assert f.quo(g) == f / g == q
+    assert f.quo(g) == f // g == q
     pytest.raises(ExactQuotientFailed, lambda: f.exquo(g))
 
     R,  x, y = ring("x,y", ZZ)
@@ -758,7 +760,7 @@ def test_PolyElement___truediv__():
 
     assert f.div(g) == divmod(f, g) == (q, r)
     assert f.rem(g) == f % g == r
-    assert f.quo(g) == f / g == q
+    assert f.quo(g) == f // g == q
     assert f.exquo(g) == q
 
     f, g = x**2 + y**2, x - y
@@ -766,7 +768,7 @@ def test_PolyElement___truediv__():
 
     assert f.div(g) == divmod(f, g) == (q, r)
     assert f.rem(g) == f % g == r
-    assert f.quo(g) == f / g == q
+    assert f.quo(g) == f // g == q
     pytest.raises(ExactQuotientFailed, lambda: f.exquo(g))
 
     f, g = x**2 + y**2, -x + y
@@ -774,7 +776,7 @@ def test_PolyElement___truediv__():
 
     assert f.div(g) == divmod(f, g) == (q, r)
     assert f.rem(g) == f % g == r
-    assert f.quo(g) == f / g == q
+    assert f.quo(g) == f // g == q
     pytest.raises(ExactQuotientFailed, lambda: f.exquo(g))
 
     f, g = x**2 + y**2, 2*x - 2*y
@@ -782,7 +784,7 @@ def test_PolyElement___truediv__():
 
     assert f.div(g) == divmod(f, g) == (q, r)
     assert f.rem(g) == f % g == r
-    assert f.quo(g) == f / g == q
+    assert f.quo(g) == f // g == q
     pytest.raises(ExactQuotientFailed, lambda: f.exquo(g))
 
     R,  x, y = ring("x,y", QQ)
@@ -792,7 +794,7 @@ def test_PolyElement___truediv__():
 
     assert f.div(g) == divmod(f, g) == (q, r)
     assert f.rem(g) == f % g == r
-    assert f.quo(g) == f / g == q
+    assert f.quo(g) == f // g == q
     assert f.exquo(g) == q
 
     f, g = x**2 + y**2, x - y
@@ -800,7 +802,7 @@ def test_PolyElement___truediv__():
 
     assert f.div(g) == divmod(f, g) == (q, r)
     assert f.rem(g) == f % g == r
-    assert f.quo(g) == f / g == q
+    assert f.quo(g) == f // g == q
     pytest.raises(ExactQuotientFailed, lambda: f.exquo(g))
 
     f, g = x**2 + y**2, -x + y
@@ -808,7 +810,7 @@ def test_PolyElement___truediv__():
 
     assert f.div(g) == divmod(f, g) == (q, r)
     assert f.rem(g) == f % g == r
-    assert f.quo(g) == f / g == q
+    assert f.quo(g) == f // g == q
     pytest.raises(ExactQuotientFailed, lambda: f.exquo(g))
 
     f, g = x**2 + y**2, 2*x - 2*y
@@ -816,11 +818,14 @@ def test_PolyElement___truediv__():
 
     assert f.div(g) == divmod(f, g) == (q, r)
     assert f.rem(g) == f % g == r
-    assert f.quo(g) == f / g == q
+    assert f.quo(g) == f // g == q
     pytest.raises(ExactQuotientFailed, lambda: f.exquo(g))
 
     pytest.raises(ZeroDivisionError, lambda: f.quo_ground(0))
     pytest.raises(ZeroDivisionError, lambda: f.quo_term(((1, 1), 0)))
+    pytest.raises(ZeroDivisionError, lambda: f.exquo_ground(0))
+
+    assert R.zero.exquo_ground(2) == R.zero
 
     assert R.zero.quo_term(((1, 0), 1)) == R.zero
     assert g.quo_term((R.zero_monom, 2)) == x - y
@@ -959,6 +964,18 @@ def test_PolyElement_rem():
     assert f.rem(G) == f.div(G)[1] == r
 
 
+def test_PolyElement_monic():
+    R, x = ring("x", ZZ)
+
+    assert (2*x + 2).monic() == x + 1
+
+    pytest.raises(ExactQuotientFailed, lambda: (2*x + 1).monic())
+
+    R, x = ring("x", QQ)
+
+    assert (2*x + 1).monic() == x + QQ(1, 2)
+
+
 def test_PolyElement_deflate():
     R, x = ring("x", ZZ)
 
@@ -1112,8 +1129,8 @@ def test_PolyElement_cofactors():
     g = 2.1*x**3
     h = 1.0*x
 
-    assert f.cofactors(g) == (h, f/h, g/h)
-    assert g.cofactors(f) == (h, g/h, f/h)
+    assert f.cofactors(g) == (h, f//h, g//h)
+    assert g.cofactors(f) == (h, g//h, f//h)
 
 
 def test_PolyElement_gcd():