#9944: categories for polynomial rings
--------------------------------------------+-------------------------------
    Reporter:  robertwb                     |         Owner:  nthiery           
                         
        Type:  defect                       |        Status:  needs_review      
                         
    Priority:  major                        |     Milestone:  sage-4.7.1        
                         
   Component:  categories                   |    Resolution:                    
                         
    Keywords:                               |   Work_issues:                    
                         
    Upstream:  N/A                          |      Reviewer:  Nicolas M. 
Thiéry, Mike Hansen, Martin Raum
      Author:  Robert Bradshaw, Simon King  |        Merged:                    
                         
Dependencies:  sage-4.7                     |  
--------------------------------------------+-------------------------------
Changes (by SimonKing):

  * status:  needs_work => needs_review
  * dependencies:  => sage-4.7


Old description:

> Currently, they're always just commutative rings.
>
> '''Apply:'''
>   1. [attachment:9944-poly-cat.patch]
>   2. [attachment:trac-9944-poly_cat_doctests.patch]
>   3. [attachment:trac-9944-poly-cat-review.patch]
>   4. [attachment:trac9944_polynomial_speedup.patch]
>   5. [attachment:trac9944_abvar_endomorphism.patch]

New description:

 Currently, they're always just commutative rings.

 '''Apply:'''
   1. [attachment:9944-poly-cat.patch]
   2. [attachment:trac-9944-poly_cat_doctests.patch]
   3. [attachment:trac-9944-poly-cat-review.patch]
   4. [attachment:trac9944_polynomial_speedup.patch]
   5. [attachment:trac9944_abvar_endomorphism.patch]
   6. [attachment:trac9944_faster_and_cleaner_coercion.patch]

--

Comment:

 Finally I got my new patch to work. Here are the new features and, in
 particular, the new timings. I think it was worth the effort!

 __Polynomial Construction Functors__

 {{{
 sage: R.<x> = PolynomialRing(GF(5), sparse=True)
 sage: F,B = R.construction()
 sage: F(B) is R
 True   # was False
 }}}

 __zero_element__

 In various places, constructions such as self.parent()(0) are used. It
 should be more efficient to have self.parent().zero_element() instead, in
 particular if this is cached using the improved cached methods from
 #11115.

 That means I had to introduce zero_element() for various classes, mostly
 in sage/modular.

 __Fix zero element of free module homomorphisms__

 The following used to fail with an error:
 {{{
 sage: V = span([[1/2,1,1],[3/2,2,1],[0,0,1]],ZZ)
 sage: V.Hom(V).zero()
 Free module morphism defined by the matrix
 [0 0 0]
 [0 0 0]
 [0 0 0]
 Domain: Free module of degree 3 and rank 3 over Integer Ring
 Echelon ...
 Codomain: Free module of degree 3 and rank 3 over Integer Ring
 Echelon ...
 }}}

 __Calling any parent with None should return zero__

 This used to be true for finite prime fields, but failed with an error
 for finite non-prime fields:
 {{{
 sage: GF(5)(None)
 0
 sage: GF(5^2,'a')(None)
 0
 }}}

 __Implement/improve `_new_constant_poly` for various polynomial classes__

 This is the main reason why the timings stated below have improved. I
 thought that `_new_constant_poly` should be a method of a polynomial ring,
 but I think it should better stay as a method of polynomials: Polynomials
 are often implemented in Cython, but polynomial rings in Python.

 In order to get a little bit of more speed, I introduce another parameter
 to `_new_constant_poly`, namely the parent in which the new polynomial is
 supposed to be created. This is because often the parent `P` of a
 polynomial `self` is already known when calling
 `self._new_constant_poly(a, P)`, so that it would be a waste of time to
 call `self.parent()` internally to determine the parent.

 __Improve Polynomial Base Injection Morphisms and use it for coercion__

 Conversion into a polynomial ring P from its base ring occurs frequently
 and should be as quick as possible.

 I had already improved the performance in the old patch version. However,
 it turned out to be better to use `_new_constant_poly`, rather than always
 using multiplication with `P.one()`.

 The rule is now: If `P.an_element()` has a `_new_constant_poly` method
 then it is used. Otherwise, if one can construct a one element in `P`
 without calling coercion, and if it has `_rmul_` and if `_rmul_` does not
 return `None` then it is used. Otherwise, `P._element_constructor_` is
 used.

 Polynomial base injection morphisms are now always registered as coercion.

 __Call method for compiled polynomials__

 The documentation for compiled polynomials states that it can be called,
 although the cdef method `.eval(...)` has less overhead. That was not
 true, there has been no `__call__` method. I added one.

 __Constant polynomial section__

 It was stated that it uses the `constant_coefficient` method, which can be
 optimized for a particulare polynomial type. However, in fact a generic
 `constant_coefficient` method was '''explicitly''' called, even if a
 polynomial type did provide a more efficient method. That has now changed.

 __Sparse versus dense versus differently implemented polynomial rings__

 A univariate polynomial ring can be sparse or dense, and if it is dense
 and over `ZZ` (or also `QQ`) they can be implemented with `FLINT` or
 `NTL`. Dense and sparse rings used to be equal, but they were not
 identical - a violation to the unique parent assumption.

 Moreover, in the multivariate case, the `implementation` and `sparse`
 arguments had no effect on the resulting ring, but were used in the cache
 key, yielding another violation of the unique parent assumption.

 I resolved these violations. I was not sure whether one should silently
 ignore arguments that are not used, or should raise an error if they are
 provided. I decided to ignore `sparse` if it is not supported, and raise
 an error for dense or multivariate rings if an implementation is not
 supported.

 We have, e.g.:
 {{{
 sage: S.<x,y> = PolynomialRing(ZZ,sparse=True)
 sage: S is ZZ['x','y']
 True  # used to be False
 sage: R.<x> = PolynomialRing(ZZ,sparse=True,implementation='FLINT')
 sage: S.<x> = PolynomialRing(ZZ,sparse=True,implementation='NTL')
 sage: R is S
 True  # used to be False
 sage: R == ZZ['x']
 False # used to be True
 sage: S.<x,y> = PolynomialRing(ZZ, implementation='NTL')
 Traceback (most recent call last):
 ...
 ValueError: The NTL implementation is not known for multivariate
 polynomial rings
 }}}

 The last example used to return a ring that was equal but not identic to
 `ZZ['x','y']`!

 Polynomial rings are now equal if and only if they are identical.
 Coercions exist from the non-default to the default version of a ring
 (hence, from sparse to dense, from NTL to FLINT.
 {{{
 sage: R.<x> = PolynomialRing(ZZ)
 sage: S.<x> = PolynomialRing(ZZ, implementation='NTL')
 sage: R == S
 False
 sage: R.has_coerce_map_from(S)
 True
 sage: S.has_coerce_map_from(R)
 False
 sage: S.<x> = PolynomialRing(ZZ, sparse=True)
 sage: R == S
 False
 sage: R.has_coerce_map_from(S)
 True
 sage: S.has_coerce_map_from(R)
 False
 }}}

 By consequence, the parent of a sum of polynomials is now unique - it used
 to depend on the summation order if dense and sparse summands were
 involved.

 __Documentation and examples__

 I think all changes are covered by doctests. Occasionally I fixed wrongly
 formatted docstrings.

 __Performance__

 Here are the new timings for the examples that we had discussed above. I
 use sage-4.7.alpha5 with the patches from this ticket applied, and I
 compare with timings that I had obtained with an unpatched version of
 sage-4.7.alpha5

 There is no significant change in the startup time: I got `1.253` for
 sage.all in unpatched sage, but the margin of error seems rather wide.
 {{{
 $ sage -startuptime
 ...
 == Slowest (including children) ==
 1.100 sage.all (None)
 0.279 sage.schemes.all (sage.all)
 0.178 twisted.persisted.styles (sage.all)
 0.164 elliptic_curves.all (sage.schemes.all)
 0.162 ell_rational_field (elliptic_curves.all)
 0.150 ell_number_field (ell_rational_field)
 ...
 }}}

 Here is the example brought up by Jeroen. There is now a drastic speedup
 were previously was a drastic slow down:
 {{{
 sage: S = GF(9,'a')
 sage: %time for n in range(8): S = PolynomialRing(S,'w')
 CPU times: user 0.03 s, sys: 0.00 s, total: 0.03 s
 Wall time: 0.03 s
 # unpatched: 0.04 s
 sage: len(S.variable_names_recursive())
 8
 sage: timeit("S(0)")
 625 loops, best of 3: 27.9 µs per loop
 # with only the other patches: 83 ms
 # unpatched: 121 µs
 }}}

 Here is the example brought up by Martin Raum:
 {{{
 sage: R = PolynomialRing(ZZ, ['a' + str(n) for n in range(10000)])
 sage: x = R.gen(0)
 sage: timeit('(2*x - 1)^2 + 5', number = 10^4)
 10000 loops, best of 3: 58.2 µs per loop
 # unpatched: 66.9 µs
 }}}

 Here are the arithmetic examples that I had provided earlier:
 {{{
 sage: R.<x> = ZZ[]
 sage: timeit('(2*x-1)^2+5', number=10^4)
 10000 loops, best of 3: 8.58 µs per loop
 # unpatched: 23.6 µs
 sage: R.<x> = QQ[]
 sage: timeit('(2*x-1)^2+5', number=10^4)
 10000 loops, best of 3: 8.4 µs per loop
 # unpatched: 25.8 µs
 sage: R.<x> = GF(3)[]   #
 sage.rings.polynomial.polynomial_zmod_flint.Polynomial_zmod_flint
 sage: timeit('(2*x-1)^2+5', number=10^4)
 10000 loops, best of 3: 66.4 µs per loop
 # unpatched: 90.1 µs
 sage: R.<x> = QQ['t'][]
 sage: timeit('(2*x-1)^2+5', number=10^4)
 10000 loops, best of 3: 75.4 µs per loop
 # unpatched: 117 µs
 sage: R.<x,y> = ZZ[]  #
 sage.rings.polynomial.multi_polynomial_libsingular.MPolynomial_libsingular
 sage: timeit('(2*x-1)^2+5', number=10^4)
 10000 loops, best of 3: 7.85 µs per loop
 # unpatched: 13.6 µs
 sage: R.<x,y> = QQ[]
 sage: timeit('(2*x-1)^2+5', number=10^4)
 10000 loops, best of 3: 7.33 µs per loop
 # unpatched: 16.9 µs
 sage: R.<x,y> = GF(3)[]
 sage: timeit('(2*x-1)^2+5', number=10^4)
 10000 loops, best of 3: 6.59 µs per loop
 # unpatched: 11.2 µs
 sage: R.<x,y> = QQ['t'][]
 sage: timeit('(2*x-1)^2+5', number=10^4)
 10000 loops, best of 3: 158 µs per loop
 # unpatched: 251 µs
 sage: R.<x,y> = Qp(3)[]
 sage: timeit('(2*x-1)^2+5', number=10^4)
 10000 loops, best of 3: 488 µs per loop
 # unpatched: 521 µs
 sage: R.<x> = Qp(3)[]
 sage: timeit('(2*x-1)^2+5', number=10^4)
 10000 loops, best of 3: 894 µs per loop
 # unpatched: 1.06 ms
 sage: R.<x> = PolynomialRing(GF(9,'a'), sparse=True)
 sage: timeit('(2*x-1)^2+5', number=10^4)
 10000 loops, best of 3: 236 µs per loop
 # unpatched: 265 µs
 }}}

 So, in '''all''' examples there is a noticeable speedup.

 __Conclusion__

 The new patch cleans coercion of polynomial rings, by enforcing uniqueness
 of parents.

 It considerably improves the performance, even when comparing with the
 improvements that were introduced in the previous patches.

 `sage -testall -long` passed. So, it is finally ready for review again.

 Depends on sage-4.7

 Apply Apply 9944-poly-cat.patch 9944-poly-cat-doctests.patch trac-9944
 -poly-cat-review.patch trac9944_polynomial_speedup.patch
 trac9944_abvar_endomorphism.patch
 trac9944_faster_and_cleaner_coercion.patch

-- 
Ticket URL: <http://trac.sagemath.org/sage_trac/ticket/9944#comment:59>
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.

Reply via email to