# HG changeset patch
# User Simon King
# Date 1297947839 3600
# Node ID 6289126bdd0bdd30ed9b7467572af6bf5e80030b
# Parent fb00ec75853019eb9799fd863b193fe82ee97c74
#10771: gcd/lcm for fraction fields of UFDs and for general fields.
In the case of fraction fields, it restricts to gcd/lcm in the base ring.
For general fields, properly restrict to ZZ, but otherwise return 0/1.
diff git a/sage/categories/fields.py b/sage/categories/fields.py
 a/sage/categories/fields.py
+++ b/sage/categories/fields.py
@@ 137,4 +137,115 @@
return True
class ElementMethods:
 pass
+ # Fields are unique factorization domains, so, there is gcd and lcm
+ # Of course, in general gcd and lcm in a field are not very interesting.
+ # However, they should be implemented!
+ def gcd(self,other):
+ """
+ Greatest common divisor.
+
+ NOTE:
+
+ Since we are in a field and the greatest common divisor is
+ only determined up to a unit, it is correct to either return
+ zero or one. Note that fraction fields of unique factorization
+ domains provide a more sophisticated gcd.
+
+ EXAMPLES::
+
+ sage: GF(5)(1).gcd(GF(5)(1))
+ 1
+ sage: GF(5)(1).gcd(GF(5)(0))
+ 1
+ sage: GF(5)(0).gcd(GF(5)(0))
+ 0
+
+ For fields of characteristic zero (i.e., containing the
+ integers as a subring), evaluation in the integer ring is
+ attempted. This is for backwards compatibility::
+
+ sage: gcd(6.0,8); gcd(6.0,8).parent()
+ 2
+ Integer Ring
+
+ If this fails, we resort to the default we see above::
+
+ sage: gcd(6.0*CC.0,8*CC.0); gcd(6.0*CC.0,8*CC.0).parent()
+ 1.00000000000000
+ Complex Field with 53 bits of precision
+
+ AUTHOR:
+
+  Simon King (201102): Trac ticket #10771
+
+ """
+ P = self.parent()
+ try:
+ other = P(other)
+ except (TypeError, ValueError):
+ raise ArithmeticError, "The second argument can not be interpreted in the parent of the first argument. Can't compute the gcd"
+ from sage.rings.integer_ring import ZZ
+ if ZZ.is_subring(P):
+ try:
+ return ZZ(self).gcd(ZZ(other))
+ except TypeError:
+ pass
+ # there is no custom gcd, so, we resort to something that always exists
+ # (that's new behaviour)
+ if self==0 and other==0:
+ return P.zero()
+ return P.one()
+
+ def lcm(self,other):
+ """
+ Least common multiple.
+
+ NOTE:
+
+ Since we are in a field and the least common multiple is
+ only determined up to a unit, it is correct to either return
+ zero or one. Note that fraction fields of unique factorization
+ domains provide a more sophisticated lcm.
+
+ EXAMPLES::
+
+ sage: GF(2)(1).lcm(GF(2)(0))
+ 0
+ sage: GF(2)(1).lcm(GF(2)(1))
+ 1
+
+ If the field contains the integer ring, it is first
+ attempted to compute the gcd there::
+
+ sage: lcm(15.0,12.0); lcm(15.0,12.0).parent()
+ 60
+ Integer Ring
+
+ If this fails, we resort to the default we see above::
+
+ sage: lcm(6.0*CC.0,8*CC.0); lcm(6.0*CC.0,8*CC.0).parent()
+ 1.00000000000000
+ Complex Field with 53 bits of precision
+ sage: lcm(15.2,12.0)
+ 1.00000000000000
+
+ AUTHOR:
+
+  Simon King (201102): Trac ticket #10771
+
+ """
+ P = self.parent()
+ try:
+ other = P(other)
+ except (TypeError, ValueError):
+ raise ArithmeticError, "The second argument can not be interpreted in the parent of the first argument. Can't compute the lcm"
+ from sage.rings.integer_ring import ZZ
+ if ZZ.is_subring(P):
+ try:
+ return ZZ(self).lcm(ZZ(other))
+ except TypeError:
+ pass
+ # there is no custom lcm, so, we resort to something that always exists
+ if self==0 or other==0:
+ return P.zero()
+ return P.one()
diff git a/sage/categories/quotient_fields.py b/sage/categories/quotient_fields.py
 a/sage/categories/quotient_fields.py
+++ b/sage/categories/quotient_fields.py
@@ 52,6 +52,164 @@
def denominator(self):
pass
+ def gcd(self,other):
+ """
+ Greatest common divisor
+
+ NOTE:
+
+ In a field, the greatest common divisor is not very
+ informative, as it is only determined up to a unit. But in
+ the fraction field of an integral domain that provides
+ both gcd and lcm, it is possible to be a bit more specific
+ and define the gcd uniquely up to a unit of the base ring
+ (rather than in the fraction field).
+
+ AUTHOR:
+
+  Simon King (201102): See trac ticket #10771
+
+ EXAMPLES::
+
+ sage: R.=QQ[]
+ sage: p = (1+x)^3*(1+2*x^2)/(1x^5)
+ sage: q = (1+x)^2*(1+3*x^2)/(1x^4)
+ sage: factor(p)
+ (2) * (x  1)^1 * (x + 1)^3 * (x^2 + 1/2) * (x^4 + x^3 + x^2 + x + 1)^1
+ sage: factor(q)
+ (3) * (x  1)^1 * (x + 1) * (x^2 + 1)^1 * (x^2 + 1/3)
+ sage: gcd(p,q)
+ (x + 1)/(x^7 + x^5  x^2  1)
+ sage: factor(gcd(p,q))
+ (x  1)^1 * (x + 1) * (x^2 + 1)^1 * (x^4 + x^3 + x^2 + x + 1)^1
+ sage: factor(gcd(p,1+x))
+ (x  1)^1 * (x + 1) * (x^4 + x^3 + x^2 + x + 1)^1
+ sage: factor(gcd(1+x,q))
+ (x  1)^1 * (x + 1) * (x^2 + 1)^1
+
+ TESTS:
+
+ The following tests that the fraction field returns a correct gcd
+ even if the base ring does not provide lcm and gcd::
+
+ sage: R = ZZ.extension(x^2+5,names='q'); R
+ Order in Number Field in q with defining polynomial x^2 + 5
+ sage: R.1
+ q
+ sage: gcd(R.1,R.1)
+ Traceback (most recent call last):
+ ...
+ TypeError: unable to find gcd of q and q
+ sage: (R.1/1).parent()
+ Number Field in q with defining polynomial x^2 + 5
+ sage: gcd(R.1/1,R.1)
+ 1
+ sage: gcd(R.1/1,0)
+ 1
+ sage: gcd(R.zero(),0)
+ 0
+
+ """
+ try:
+ other = self.parent()(other)
+ except (TypeError, ValueError):
+ raise ArithmeticError, "The second argument can not be interpreted in the parent of the first argument. Can't compute the gcd"
+ try:
+ selfN = self.numerator()
+ selfD = self.denominator()
+ selfGCD = selfN.gcd(selfD)
+ otherN = other.numerator()
+ otherD = other.denominator()
+ otherGCD = otherN.gcd(otherD)
+ selfN = selfN // selfGCD
+ selfD = selfD // selfGCD
+ otherN = otherN // otherGCD
+ otherD = otherD // otherGCD
+ return selfN.gcd(otherN)/selfD.lcm(otherD)
+ except (AttributeError, NotImplementedError, TypeError, ValueError):
+ if self==0 and other==0:
+ return self.parent().zero()
+ return self.parent().one()
+
+ def lcm(self,other):
+ """
+ Least common multiple
+
+ NOTE:
+
+ In a field, the least common multiple is not very
+ informative, as it is only determined up to a unit. But in
+ the fraction field of an integral domain that provides
+ both gcd and lcm, it is reasonable to be a bit more
+ specific and to define the least common multiple so that
+ it restricts to the usual least common multiple in the
+ base ring and is unique up to a unit of the base ring
+ (rather than up to a unit of the fraction field).
+
+ AUTHOR:
+
+  Simon King (201102): See trac ticket #10771
+
+ EXAMPLES::
+
+ sage: R.=QQ[]
+ sage: p = (1+x)^3*(1+2*x^2)/(1x^5)
+ sage: q = (1+x)^2*(1+3*x^2)/(1x^4)
+ sage: factor(p)
+ (2) * (x  1)^1 * (x + 1)^3 * (x^2 + 1/2) * (x^4 + x^3 + x^2 + x + 1)^1
+ sage: factor(q)
+ (3) * (x  1)^1 * (x + 1) * (x^2 + 1)^1 * (x^2 + 1/3)
+ sage: factor(lcm(p,q))
+ (x  1)^1 * (x + 1)^3 * (x^2 + 1/3) * (x^2 + 1/2)
+ sage: factor(lcm(p,1+x))
+ (x + 1)^3 * (x^2 + 1/2)
+ sage: factor(lcm(1+x,q))
+ (x + 1) * (x^2 + 1/3)
+
+ TESTS:
+
+ The following tests that the fraction field returns a correct lcm
+ even if the base ring does not provide lcm and gcd::
+
+ sage: R = ZZ.extension(x^2+5,names='q'); R
+ Order in Number Field in q with defining polynomial x^2 + 5
+ sage: R.1
+ q
+ sage: lcm(R.1,R.1)
+ Traceback (most recent call last):
+ ...
+ TypeError: unable to find lcm of q and q
+ sage: (R.1/1).parent()
+ Number Field in q with defining polynomial x^2 + 5
+ sage: lcm(R.1/1,R.1)
+ 1
+ sage: lcm(R.1/1,0)
+ 0
+ sage: lcm(R.zero(),0)
+ 0
+
+ """
+ try:
+ other = self.parent()(other)
+ except (TypeError, ValueError):
+ raise ArithmeticError, "The second argument can not be interpreted in the parent of the first argument. Can't compute the lcm"
+ try:
+ selfN = self.numerator()
+ selfD = self.denominator()
+ selfGCD = selfN.gcd(selfD)
+ otherN = other.numerator()
+ otherD = other.denominator()
+ otherGCD = otherN.gcd(otherD)
+ selfN = selfN // selfGCD
+ selfD = selfD // selfGCD
+ otherN = otherN // otherGCD
+ otherD = otherD // otherGCD
+ return selfN.lcm(otherN)/selfD.gcd(otherD)
+ except (AttributeError, NotImplementedError, TypeError, ValueError):
+ if self==0 or other==0:
+ return self.parent().zero()
+ return self.parent().one()
+
def factor(self, *args, **kwds):
"""
Return the factorization of ``self`` over the base ring.
@@ 214,7 +372,7 @@
Returns the derivative of this rational function with respect to the
variable ``var``.
 Over an ring with a working GCD implementation, the derivative of a
+ Over an ring with a working gcd implementation, the derivative of a
fraction `f/g`, supposed to be given in lowest terms, is computed as
`(f'(g/d)  f(g'/d))/(g(g'/d))`, where `d` is a greatest common
divisor of `f` and `g`.
diff git a/sage/rings/arith.py b/sage/rings/arith.py
 a/sage/rings/arith.py
+++ b/sage/rings/arith.py
@@ 1412,6 +1412,11 @@
Additional keyword arguments are passed to the respectively called
methods.
+
+ OUTPUT:
+
+ The given elements are first coerced into a common parent. Then,
+ their greatest common divisor *in that common parent* is returned.
EXAMPLES::
@@ 1419,8 +1424,8 @@
1
sage: GCD(97*10^15, 19^20*97^2)
97
 sage: GCD(2/3, 4/3)
 1
+ sage: GCD(2/3, 4/5)
+ 2/15
sage: GCD([2,4,6,8])
2
sage: GCD(srange(0,10000,10)) # fast !!
@@ 1451,17 +1456,37 @@
0
sage: type(gcd([]))
+
+ TESTS:
+
+ The following shows that indeed coercion takes place before computing
+ the gcd. This behaviour was introduced in trac ticket #10771::
+
+ sage: R.=QQ[]
+ sage: S.=ZZ[]
+ sage: p = S.random_element()
+ sage: q = R.random_element()
+ sage: parent(gcd(1/p,q))
+ Fraction Field of Univariate Polynomial Ring in x over Rational Field
+ sage: parent(gcd([1/p,q]))
+ Fraction Field of Univariate Polynomial Ring in x over Rational Field
+
+
"""
# Most common use case first:
if b is not None:
 if hasattr(a, "gcd"):
 return a.gcd(b, **kwargs)
 else:
+ from sage.structure.element import get_coercion_model
+ cm = get_coercion_model()
+ a,b = cm.canonical_coercion(a,b)
+ try:
+ GCD = a.gcd
+ except AttributeError:
try:
return ZZ(a).gcd(ZZ(b))
except TypeError:
raise TypeError, "unable to find gcd of %s and %s"%(a,b)

+ return GCD(b, **kwargs)
+
from sage.structure.sequence import Sequence
seq = Sequence(a)
U = seq.universe()
@@ 1528,7 +1553,11 @@
 ``a``  a list or tuple of elements of a ring with
lcm

+
+ OUTPUT:
+
+ First, the given elements are coerced into a common parent. Then,
+ their least common multiple *in that parent* is returned.
EXAMPLES::
@@ 1545,17 +1574,40 @@
sage: v = LCM(range(1,10000)) # *very* fast!
sage: len(str(v))
4349
+
+ TESTS:
+
+ The following tests against a bug that was fixed in trac
+ ticket #10771::
+
+ sage: lcm(4/1,2)
+ 4
+
+ The following shows that indeed coercion takes place before
+ computing the least common multiple::
+
+ sage: R.=QQ[]
+ sage: S.=ZZ[]
+ sage: p = S.random_element()
+ sage: q = R.random_element()
+ sage: parent(lcm([1/p,q]))
+ Fraction Field of Univariate Polynomial Ring in x over Rational Field
+
"""
# Most common use case first:
if b is not None:
 if hasattr(a, "lcm"):
 return a.lcm(b)
 else:
+ from sage.structure.element import get_coercion_model
+ cm = get_coercion_model()
+ a,b = cm.canonical_coercion(a,b)
+ try:
+ LCM = a.lcm
+ except AttributeError:
try:
return ZZ(a).lcm(ZZ(b))
except TypeError:
 raise TypeError, "unable to find gcd of %s and %s"%(a,b)

+ raise TypeError, "unable to find lcm of %s and %s"%(a,b)
+ return LCM(b)
+
from sage.structure.sequence import Sequence
seq = Sequence(a)
U = seq.universe()
diff git a/sage/rings/polynomial/multi_polynomial.pyx b/sage/rings/polynomial/multi_polynomial.pyx
 a/sage/rings/polynomial/multi_polynomial.pyx
+++ b/sage/rings/polynomial/multi_polynomial.pyx
@@ 918,18 +918,21 @@
sage: f.content().parent()
Integer Ring
 TESTS::
+ TESTS:
+
+ Since trac ticket #10771, the gcd in QQ restricts to the
+ gcd in ZZ.
sage: R. = QQ[]
sage: f = 4*x+6*y
 sage: f.content()
 1
+ sage: f.content(); f.content().parent()
+ 2
+ Rational Field
"""
from sage.rings.arith import gcd
from sage.rings.all import ZZ

 return gcd(self.coefficients(),integer=self.parent() is ZZ)
+ return gcd(self.coefficients())
def is_generator(self):
r"""
diff git a/sage/rings/rational.pyx b/sage/rings/rational.pyx
 a/sage/rings/rational.pyx
+++ b/sage/rings/rational.pyx
@@ 896,27 +896,27 @@
from sage.rings.arith import gcd, lcm
return gcd(nums) / lcm(denoms)
 def gcd(self, other, **kwds):
 """
 Return a gcd of the rational numbers self and other.

 If self = other = 0, this is by convention 0. In all other
 cases it can (mathematically) be any nonzero rational number,
 but for simplicity we choose to always return 1.

 EXAMPLES::

 sage: gcd(1/3, 2/1)
 1
 sage: gcd(1/1, 0/1)
 1
 sage: gcd(0/1, 0/1)
 0
 """
 if self == 0 and other == 0:
 return Rational(0)
 else:
 return Rational(1)
+# def gcd_rational(self, other, **kwds):
+# """
+# Return a gcd of the rational numbers self and other.
+#
+# If self = other = 0, this is by convention 0. In all other
+# cases it can (mathematically) be any nonzero rational number,
+# but for simplicity we choose to always return 1.
+#
+# EXAMPLES::
+#
+# sage: (1/3).gcd_rational(2/1)
+# 1
+# sage: (1/1).gcd_rational(0/1)
+# 1
+# sage: (0/1).gcd_rational(0/1)
+# 0
+# """
+# if self == 0 and other == 0:
+# return Rational(0)
+# else:
+# return Rational(1)
def valuation(self, p):
r"""
diff git a/sage/rings/ring.pyx b/sage/rings/ring.pyx
 a/sage/rings/ring.pyx
+++ b/sage/rings/ring.pyx
@@ 1612,13 +1612,19 @@
2^3 * 11
In a field, any nonzero element is a GCD of any nonempty set
 of elements. For concreteness, Sage returns 1 in these cases::
+ of nonzero elements. In previous versions, Sage used to return
+ 1 in the case of the rational field. However, since trac
+ ticket #10771, the rational field is considered as the
+ *fraction field* of the integer ring. For the fraction field
+ of an integral domain that provides both GCD and LCM, it is
+ possible to pick a GCD that is compatible with the GCD of the
+ base ring::
sage: QQ.gcd(ZZ(42), ZZ(48)); type(QQ.gcd(ZZ(42), ZZ(48)))
 1
+ 6
sage: QQ.gcd(1/2, 1/3)
 1
+ 1/6
Polynomial rings over fields are GCD domains as well. Here is a simple
example over the ring of polynomials over the rationals as well as
diff git a/sage/stats/basic_stats.py b/sage/stats/basic_stats.py
 a/sage/stats/basic_stats.py
+++ b/sage/stats/basic_stats.py
@@ 165,7 +165,7 @@
sage: std([])
NaN
sage: std([I, sqrt(2), 3/5])
 sqrt(1/450*(5*sqrt(2) + 5*I  6)^2 + 1/450*(5*sqrt(2)  10*I + 3)^2 + 1/450*(10*sqrt(2)  5*I  3)^2)
+ sqrt(1/450*(5*sqrt(2) + 10*I  3)^2 + 1/450*(5*sqrt(2)  5*I + 6)^2 + 1/450*(10*sqrt(2)  5*I  3)^2)
sage: std([RIF(1.0103, 1.0103), RIF(2)])
0.6998235813403261?
sage: import numpy
@@ 230,7 +230,7 @@
sage: variance([])
NaN
sage: variance([I, sqrt(2), 3/5])
 1/450*(5*sqrt(2) + 5*I  6)^2 + 1/450*(5*sqrt(2)  10*I + 3)^2 + 1/450*(10*sqrt(2)  5*I  3)^2
+ 1/450*(5*sqrt(2) + 10*I  3)^2 + 1/450*(5*sqrt(2)  5*I + 6)^2 + 1/450*(10*sqrt(2)  5*I  3)^2
sage: variance([RIF(1.0103, 1.0103), RIF(2)])
0.4897530450000000?
sage: import numpy