#8800: Doctest coverage of categories - numerous coercion fixes
--------------------------+-------------------------------------------------
Reporter: SimonKing | Owner: Simon King
Type: defect | Status: needs_work
Priority: major | Milestone: sage-4.6.1
Component: categories | Keywords: categories doctests
Author: Simon King | Upstream: N/A
Reviewer: | Merged:
Work_issues: |
--------------------------+-------------------------------------------------
Description changed by SimonKing:
Old description:
> According to William, the doctest coverage of categories is too low:
>
> {{{
> action.pyx: 0% (0 of 31)
> functor.pyx: 17% (3 of 17)
> map.pyx: 27% (10 of 37)
> morphism.pyx: 20% (5 of 24)
> pushout.py: 24% (19 of 77)
> }}}
> Trying to add doc tests, I actually found a bug:
>
> {{{
> sage: abgrps = CommutativeAdditiveGroups()
> sage: ForgetfulFunctor(abgrps, abgrps)
> ---------------------------------------------------------------------------
> TypeError Traceback (most recent call
> last)
>
> /home/king/SAGE/patches/doku/english/<ipython console> in <module>()
>
> /home/king/SAGE/sage-4.3.1/local/lib/python2.6/site-
> packages/sage/categories/functor.so in
> sage.categories.functor.ForgetfulFunctor
> (sage/categories/functor.c:2083)()
>
> TypeError: IdentityFunctor() takes exactly one argument (2 given)
> }}}
> The forgetful functor should coincide with the identity functor, but
> inside ``ForgetfulFunctor``, the latter is called in the wrong way.
New description:
According to William, the doctest coverage of categories is too low:
{{{
action.pyx: 0% (0 of 31)
functor.pyx: 17% (3 of 17)
map.pyx: 27% (10 of 37)
morphism.pyx: 20% (5 of 24)
pushout.py: 24% (19 of 77)
}}}
The original purpose of this ticket was to provide full doctest coverage
for `functor.pyx` and `pushout.py`. '''The doctest coverage of both files
is now 100%'''.
However, the attempt to create meaningful doctests uncovered many bugs in
various parts of Sage, and also motivated the implementation of coercion
for various algebraic structures for which this has not been done before.
This a-posteriori ticket description lists the bugs killed and the
features added by the patch, which should apply (with a little fuzz) after
the patch from #8807. For more details on the bugs, see the comments
below.
1. Bug: Creating `ForgetfulFunctor` fails.
Was:
{{{
sage: abgrps = CommutativeAdditiveGroups()
sage: ForgetfulFunctor(abgrps, abgrps)
---------------------------------------------------------------------------
TypeError Traceback (most recent call
last)
/home/king/SAGE/patches/doku/english/<ipython console> in <module>()
/home/king/SAGE/sage-4.3.1/local/lib/python2.6/site-
packages/sage/categories/functor.so in
sage.categories.functor.ForgetfulFunctor
(sage/categories/functor.c:2083)()
TypeError: IdentityFunctor() takes exactly one argument (2 given)
}}}
Now:
{{{
sage: abgrps = CommutativeAdditiveGroups()
sage: ForgetfulFunctor(abgrps, abgrps)
The identity functor on Category of commutative additive groups
}}}
2. Bug: Applying `ForgetfulFunctor` returns `None`.
Was:
{{{
sage: fields = Fields()
sage: rings = Rings()
sage: F = ForgetfulFunctor(fields,rings)
sage: F(QQ)
}}}
Now:
{{{
sage: fields = Fields()
sage: rings = Rings()
sage: F = ForgetfulFunctor(fields,rings)
sage: F(QQ)
Rational Field
}}}
3. Bug: Applying a functor does not complain if the argument is not
contained in the domain.
Was:
{{{
sage: fields = Fields()
sage: rings = Rings()
sage: F = ForgetfulFunctor(fields,rings)
# Yields None, see previous bug
sage: F(ZZ['x','y'])
}}}
Now:
{{{
sage: fields = Fields()
sage: rings = Rings()
sage: F = ForgetfulFunctor(fields,rings)
sage: F(ZZ['x','y'])
Traceback (most recent call last):
...
TypeError: x (=Multivariate Polynomial Ring in x, y over Integer Ring) is
not in Category of fields
}}}
4. Bug: Comparing identity functor with any functor only checks domain and
codomain
Was:
{{{
sage: F = QQ['x'].construction()[0]
sage: F
Poly[x]
sage: F == IdentityFunctor(Rings())
False
sage: IdentityFunctor(Rings()) == F
True
}}}
Now:
{{{
sage: F = QQ['x'].construction()[0]
sage: F
Poly[x]
sage: F == IdentityFunctor(Rings())
False
sage: IdentityFunctor(Rings()) == F
False
}}}
5. Bug: Comparing identity functor with anything that is not a functor
produces an error
Was:
{{{
sage: IdentityFunctor(Rings()) == QQ
Traceback (most recent call last):
...
AttributeError: 'RationalField_with_category' object has no attribute
'domain'
}}}
Now:
{{{
sage: IdentityFunctor(Rings()) == QQ
False
}}}
6. Bug: The matrix functor is ill defined; moreover, ill-definedness does
not result in an error.
Was:
{{{
sage: F = MatrixSpace(ZZ,2,3).construction()[0]
sage: F(RR) in F.codomain()
False
# The codomain is wrong for non-square matrices!
sage: F.codomain()
Category of rings
}}}
Now:
{{{
sage: F = MatrixSpace(ZZ,2,3).construction()[0]
sage: F.codomain()
Category of commutative additive groups
sage: F(RR) in F.codomain()
True
sage: F = MatrixSpace(ZZ,2,2).construction()[0]
sage: F.codomain()
Category of rings
sage: F(RR) in F.codomain()
True
}}}
7. Bug: Wrong domain for `VectorFunctor`; and again, functors don't test
if the domain is appropriate
Was:
{{{
sage: F = FreeModule(ZZ,3).construction()[0]
sage: F
VectorFunctor
sage: F.domain()
Category of objects
sage: F.codomain()
Category of objects
sage: Set([1,2,3]) in F.domain()
True
sage: F(Set([1,2,3]))
Traceback (most recent call last):
...
AttributeError: 'Set_object_enumerated' object has no attribute
'is_commutative'
}}}
Now:
{{{
sage: F = FreeModule(ZZ,3).construction()[0]
sage: F
VectorFunctor
sage: F.domain()
Category of commutative rings
sage: Set([1,2,3]) in F.domain()
False
sage: F(Set([1,2,3]))
Traceback (most recent call last):
...
TypeError: x (={1, 2, 3}) is not in Category of commutative rings
}}}
8. Bug: `BlackBoxConstructionFunctor` is completely unusable
`BlackBoxConstructionFunctor` should be a class, but is defined as a
function. Moreover, the given init method is not using the init method of
`ConstructionFunctor`. And the cmp method would raise an error if the
second argument has no attribute `.box`.
The following did not work at all:
{{{
sage: from sage.categories.pushout import BlackBoxConstructionFunctor
sage: FG = BlackBoxConstructionFunctor(gap)
sage: FS = BlackBoxConstructionFunctor(singular)
sage: FG
BlackBoxConstructionFunctor
sage: FG(ZZ)
Integers
sage: FG(ZZ).parent()
Gap
sage: FS(QQ['t'])
// characteristic : 0
// number of vars : 1
// block 1 : ordering lp
// : names t
// block 2 : ordering C
sage: FG == FS
False
sage: FG == loads(dumps(FG))
True
}}}
9. Nitpicking: The `LocalizationFunctor` is nowhere used (yet)
Hence, I removed it.
10. Bug / New Feature: Make completion and and fraction field construction
functors commute.
The result of them not commuting is the following coercion bug.
Was:
{{{
sage: R1.<x> = Zp(5)[]
sage: R2 = Qp(5)
sage: R2(1)+x
Traceback (most recent call last):
...
TypeError: unsupported operand parent(s) for '+': '5-adic Field with
capped relative precision 20' and 'Univariate Polynomial Ring in x over
5-adic Ring with capped relative precision 20'
}}}
Now:
{{{
sage: R1.<x> = Zp(5)[]
sage: R2 = Qp(5)
sage: R2(1)+x
(1 + O(5^20))*x + (1 + O(5^20))
}}}
11. New feature: Make the completion functor work on some objects that do
not provide a completion method.
The idea is to use that the completion functor may commute with the
construction of the given argument. That may safe the day.
Was:
{{{
sage: P.<x> = ZZ[]
sage: C = P.completion(x).construction()[0]
sage: R = FractionField(P)
sage: hasattr(R,'completion')
False
sage: C(R)
Traceback (most recent call last):
...
AttributeError: 'FractionField_generic' object has no attribute
'completion'
}}}
Now:
{{{
sage: P.<x> = ZZ[]
sage: C = P.completion(x).construction()[0]
sage: R = FractionField(P)
sage: hasattr(R,'completion')
False
sage: C(R)
Fraction Field of Power Series Ring in x over Integer Ring
}}}
12. Bug / new feature: Coercion for free modules, taking into account a
user-defined inner product
Was:
{{{
sage: P.<t> = ZZ[]
sage: M1 = FreeModule(P,3)
sage: M2 = QQ^3
sage: M2([1,1/2,1/3]) + M1([t,t^2+t,3]) # This is ok
(t + 1, t^2 + t + 1/2, 10/3)
sage: M3 = FreeModule(P,3, inner_product_matrix = Matrix(3,3,range(9)))
sage: M2([1,1/2,1/3]) + M3([t,t^2+t,3]) # This is ok
(t + 1, t^2 + t + 1/2, 10/3)
# The user defined inner product matrix is lost! Bug
sage: parent(M2([1,1/2,1/3]) + M3([t,t^2+t,3])).inner_product_matrix()
[1 0 0]
[0 1 0]
[0 0 1]
}}}
Now:
{{{
sage: parent(M2([1,1/2,1/3]) + M3([t,t^2+t,3])).inner_product_matrix()
[0 1 2]
[3 4 5]
[6 7 8]
}}}
However, the real problem is that modules are not part of the coercion
model. I tried to implement it, but that turned out to be a can of worms.
So, '''I suggest to deal with it on a different ticket'''. Here is one bug
that isn't removed, yet:
{{{
sage: M4 = FreeModule(P,3, inner_product_matrix =
Matrix(3,3,[1,1,1,0,1,1,0,0,1]))
sage: M3([t,1,t^2]) + M4([t,t^2+t,3]) # This should result in an error
(2*t, t^2 + t + 1, t^2 + 3)
}}}
Note that there should be no coercion between `M3` and `M4`, since they
have different user-defined inner product matrices.
13. Bug / new feature: Quotient rings of univariate polynomial rings do
not have a construction method.
Was:
{{{
sage: P.<x> = QQ[]
sage: Q1 = P.quo([(x^2+1)^2*(x^2-3)])
sage: Q2 = P.quo([(x^2+1)^2*(x^5+3)])
sage: from sage.categories.pushout import pushout
sage: pushout(Q1,Q2)
Traceback (most recent call last):
...
CoercionException: No common base
}}}
Now:
{{{
sage: P.<x> = QQ[]
sage: Q1 = P.quo([(x^2+1)^2*(x^2-3)])
sage: Q2 = P.quo([(x^2+1)^2*(x^5+3)])
sage: from sage.categories.pushout import pushout
sage: pushout(Q1,Q2)
Univariate Quotient Polynomial Ring in xbar over Rational Field with
modulus x^4 + 2*x^2 + 1
}}}
14. Insufficient coercion of quotient rings, if one modulus divides the
other
Was:
{{{
sage: P5.<x> = GF(5)[]
sage: Q = P5.quo([(x^2+1)^2])
sage: P.<x> = ZZ[]
sage: Q1 = P.quo([(x^2+1)^2*(x^2-3)])
sage: Q2 = P.quo([(x^2+1)^2*(x^5+3)])
sage: Q.has_coerce_map_from(Q1)
False
}}}
Now: There is a coercion from `Q1` to `Q`.
15. Coercion of `GF(p)` versus `Integers(p)`
I am not sure if this is really a bug.
Was:
{{{
sage: from sage.categories.pushout import pushout
sage: pushout(GF(5), Integers(5))
Ring of integers modulo 5
}}}
Now
{{{
sage: from sage.categories.pushout import pushout
sage: pushout(GF(5), Integers(5))
Finite Field of size 5
}}}
16. Bug / new feature: Construction for QQbar was missing.
Now:
{{{
sage: QQbar.construction()
(AlgebraicClosureFunctor, Rational Field)
}}}
17. Bug / new feature: Construction for number fields is missing.
This became a rather complicated topic, including "coercions for embedded
versus non-embedded number fields and coercion for an order from a
coercion from the ambient field", "pushout for number fields", "comparison
of fractional ideals", "identity of residue fields". See three discussions
on sage-algebra and sage-nt
* [http://groups.google.com/group/sage-
nt/browse_thread/thread/32b65a5173f43267 Bidirectional coercions]
* [http://groups.google.com/group/sage-
nt/browse_thread/thread/5c376dbf7e99ea97 Coercions for number fields]
* [http://groups.google.com/group/sage-
nt/browse_thread/thread/54c1e33872d14334 Comparison of fractional ideals]
__Coercion__
Was:
{{{
sage: K.<r4> = NumberField(x^4-2)
sage: L1.<r2_1> = NumberField(x^2-2, embedding = r4**2)
sage: L2.<r2_2> = NumberField(x^2-2, embedding = -r4**2)
sage: r2_1+r2_2 # indirect doctest
ERROR: An unexpected error occurred while tokenizing input
The following traceback may be corrupted or invalid
The error message is: ('EOF in multi-line statement', (1109, 0))
ERROR: An unexpected error occurred while tokenizing input
The following traceback may be corrupted or invalid
The error message is: ('EOF in multi-line statement', (1109, 0))
...
sage: K.has_coerce_map_from(L1.maximal_order())
False # that's the wrong direction.
sage: L1.has_coerce_map_from(K.maximal_order())
True
}}}
Now:
{{{
sage: K.<r4> = NumberField(x^4-2)
sage: L1.<r2_1> = NumberField(x^2-2, embedding = r4**2)
sage: L2.<r2_2> = NumberField(x^2-2, embedding = -r4**2)
sage: r2_1+r2_2 # indirect doctest
0
sage: (r2_1+r2_2).parent() is L1
True
sage: (r2_2+r2_1).parent() is L2
True
sage: K.has_coerce_map_from(L1.maximal_order())
True
sage: L1.has_coerce_map_from(K.maximal_order())
False
}}}
__Pushout__
Was:
{{{
sage: P.<x> = QQ[]
sage: L.<b> = NumberField(x^8-x^4+1, embedding=CDF.0)
sage: M1.<c1> = NumberField(x^2+x+1, embedding=b^4-1)
sage: M2.<c2> = NumberField(x^2+1, embedding=-b^6)
sage: M1.coerce_map_from(M2)
sage: M2.coerce_map_from(M1)
sage: c1+c2; parent(c1+c2) #indirect doctest
Traceback (most recent call last):
...
TypeError: unsupported operand parent(s) for '+': 'Number Field in c1 with
defining polynomial x^2 + x + 1' and 'Number Field in c2 with defining
polynomial x^2 + 1'
sage: from sage.categories.pushout import pushout
sage: pushout(M1['x'],M2['x'])
Traceback (most recent call last):
...
CoercionException: No common base
}}}
Now: Note that we will only have a pushout if the codomains of the
embeddings are number fields. Hence, in the second example, we won't use
`CDF` as a pushout.
{{{
sage: P.<x> = QQ[]
sage: L.<b> = NumberField(x^8-x^4+1, embedding=CDF.0)
sage: M1.<c1> = NumberField(x^2+x+1, embedding=b^4-1)
sage: M2.<c2> = NumberField(x^2+1, embedding=-b^6)
sage: M1.coerce_map_from(M2)
sage: M2.coerce_map_from(M1)
sage: c1+c2; parent(c1+c2) #indirect doctest
-b^6 + b^4 - 1
Number Field in b with defining polynomial x^8 - x^4 + 1
sage: from sage.categories.pushout import pushout
sage: pushout(M1['x'],M2['x'])
Univariate Polynomial Ring in x over Number Field in b with defining
polynomial x^8 - x^4 + 1
sage: K.<a> = NumberField(x^3-2, embedding=CDF(1/2*I*2^(1/3)*sqrt(3) -
1/2*2^(1/3)))
sage: L.<b> = NumberField(x^6-2, embedding=1.1)
sage: L.coerce_map_from(K)
sage: K.coerce_map_from(L)
sage: pushout(K,L)
Traceback (most recent call last):
...
CoercionException: ('Ambiguous Base Extension', Number Field in a with
defining polynomial x^3 - 2, Number Field in b with defining polynomial
x^6 - 2)
}}}
__Comparison of fractional ideals / identity of Residue Fields__
Fractional ideals have a `__cmp__` method that only took into account
the Hermite normal form. In addition with coercion, we obtain:
{{{
sage: L.<b> = NumberField(x^8-x^4+1)
sage: F_2 = L.fractional_ideal(b^2-1)
sage: F_4 = L.fractional_ideal(b^4-1)
sage: F_2==F_4
True
sage: K.<r4> = NumberField(x^4-2)
sage: L.<r4> = NumberField(x^4-2, embedding=CDF.0)
sage: FK = K.fractional_ideal(K.0)
sage: FL = L.fractional_ideal(L.0)
sage: FK == FL
True
}}}
Since the residue fields of two equal fractional fields are the same
(caching), we obtain:
{{{
sage: RL = ResidueField(FL)
sage: RK = ResidueField(FK)
sage: RK is RL
True
}}}
Thus, `RK` is in fact defined with the embedded field `L`, not with the
unembedded `K`. Hence, there is no coercion from the order of `K` to `RK`.
However, ''conversion'' works (this used to fail!):
{{{
sage: OK = K.maximal_order()
sage: RK.has_coerce_map_from(OK)
False
sage: RK(OK.1)
0
}}}
Note that I also had to change some arithmetic stuff in the `_tate`
method of elliptic curves: The old implementation relied on the assumption
that fractional ideals in an embedded field and in a non-embedded field
can't be equal.
--
--
Ticket URL: <http://trac.sagemath.org/sage_trac/ticket/8800#comment:35>
Sage <http://www.sagemath.org>
Sage: Creating a Viable Open Source Alternative to Magma, Maple, Mathematica,
and MATLAB
--
You received this message because you are subscribed to the Google Groups
"sage-trac" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/sage-trac?hl=en.