Closed robertwb closed 15 years ago
A comparison
sage: n = odd_part(factorial(31))
sage: time _ = divisors(n)
CPU times: user 2.27 s, sys: 0.05 s, total: 2.32 s
Wall time: 2.33 s
sage: nn = gp(n)
sage: time _ = nn.divisors()
CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
Wall time: 0.15 s
sage: 2.33 / .15
15.5333333333333
A much simpler and faster algorithm. I'm sure there's a faster, balanced algorithm, but this is a huge improvement over what is there.
Note that this works for more than integers now too:
sage: K.<a> = QuadraticField(7)
sage: divisors(K.ideal(7))
[1, Fractional ideal (-a), Fractional ideal (7)]
sage: divisors(K.ideal(3))
[1, Fractional ideal (a - 2), Fractional ideal (-a - 2), Fractional ideal (3)]
sage: divisors(K.ideal(35))
[1, Fractional ideal (5), Fractional ideal (-a), Fractional ideal (-5*a), Fractional ideal (7), Fractional ideal (35)]
Replying to @robertwb:
Note that this works for more than integers now too:
Shouldn't that example then be added to the docstrings, too? :)
Cheers,
Michael
Of course :). I've posted an updated patch.
Before:
sage: n = odd_part(factorial(31))
sage: time _ = divisors(n)
CPU times: user 1.66 s, sys: 0.03 s, total: 1.70 s
Wall time: 1.73 s
sage: n = 928304029834092384082304982093480293849028349823948
sage: time _ = divisors(n)
CPU times: user 0.30 s, sys: 0.06 s, total: 0.35 s
Wall time: 0.46 s
sage: n = 9283040298340
sage: timeit('divisors(n)')
125 loops, best of 3: 1.75 ms per loop
After:
sage: n = odd_part(factorial(31))
sage: time _ = divisors(n)
CPU times: user 0.90 s, sys: 0.06 s, total: 0.96 s
Wall time: 0.98 s
sage: time a = gp(n).divisors()
CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
Wall time: 0.21 s
sage: n = 928304029834092384082304982093480293849028349823948
sage: time _ = divisors(n)
CPU times: user 0.31 s, sys: 0.06 s, total: 0.37 s
Wall time: 0.40 s
sage: n = 9283040298340
sage: timeit('divisors(n)')
625 loops, best of 3: 341 µs per loop
sage: divisors(int(5))
Traceback (most recent call last):
...
AttributeError: 'int' object has no attribute 'parent'
There should be a doctest about that.
For a positive review fix the code to work on int/long as input, and add a doctest about that. Regarding speeding it up to be on par with pari, that should be another ticket that must be opened before closing this one. Ideas regarding speed:
prod(e+1 for _,e in f)
one = int(1)
all = [one]
for p, e in f:
p = int(p)
...
sage: time d = divisors(n)
CPU times: user 0.23 s, sys: 0.02 s, total: 0.24 s
Wall time: 0.25 s
sage: time w = [ZZ(a) for a in d]
CPU times: user 0.24 s, sys: 0.01 s, total: 0.25 s
Wall time: 0.26 s
sage: time w = gp(n).divisors()
CPU times: user 0.00 s, sys: 0.01 s, total: 0.01 s
Wall time: 0.18 s
Interestingly, as you can see above, doing everything with python ints, then converting all the python ints back to sage ints, results in something that is twice as fast as your code on the same input.
Other remarks:
pari(n).divisors() gives an AttributeError, so I guess we never wrapped that.
Why is odd_part(factorial(31)) a RATIONAL? This seems really weird.
sage: n = odd_part(factorial(31))
sage: type(n)
<type 'sage.rings.rational.Rational'>
sage: type(factorial(31))
<type 'sage.rings.integer.Integer'>
The new code looks very like my C++ functions in eclib (there for the taking!) which include various other options such as
d^2|n
which I did once need, and so on.Just a comment.
I'll make those fixes, and write a special case for word-sized integers. It should be noted that sorting the output often takes the majority of the time
sage: sage: n = ZZ(odd_part(factorial(31)))
sage: sage: time _ = divisors(n)
CPU times: user 0.53 s, sys: 0.02 s, total: 0.54 s
Wall time: 0.55 s
sage: sage: time _ = divisors(n, sorted=False)
CPU times: user 0.15 s, sys: 0.02 s, total: 0.17 s
Wall time: 0.17 s
sage: n = factorial(25)
sage: time _ = divisors(n)
CPU times: user 1.07 s, sys: 0.05 s, total: 1.12 s
Wall time: 1.12 s
sage: time _ = divisors(n, sorted=False)
CPU times: user 0.33 s, sys: 0.05 s, total: 0.38 s
Wall time: 0.38 s
Originally I wasn't sorting the output, which was why I thought I was so much faster. (The original code sorted the output too, but it was slow enough that the sorting didn't dominate.)
It should be noted that sorting the output often takes the majority of the time
This is perhaps partly because sorting lists of GMP integers is slower than Python ints, i.e., it takes about twice as long to do a comparison:
sage: n = ZZ(odd_part(factorial(31)))
sage:
sage: m = divisors(n, sorted=False)
sage: m2 = [int(j) for j in m]
sage: time m.sort()
CPU times: user 0.33 s, sys: 0.00 s, total: 0.34 s
Wall time: 0.34 s
sage: time m2.sort()
CPU times: user 0.16 s, sys: 0.00 s, total: 0.17 s
Wall time: 0.17 s
sage: a = 349; b = 678
sage: timeit('a < b')
625 loops, best of 3: 389 ns per loop
sage: a = int(349); b = int(678)
sage: timeit('a < b')
625 loops, best of 3: 213 ns per loop
In the timeits at the bottom, probably 100ns is spent just looking up a and b in the globals...
Anyway, some of the ints for the divisors of n above are beyond long long, so this word size discussion doesn't apply. However, if we consider 25, notice that sorted takes twice as long as computing the divisors:
sage: n = ZZ(odd_part(factorial(25)))
sage: time m = divisors(n,sorted=False)
CPU times: user 0.01 s, sys: 0.00 s, total: 0.01 s
Wall time: 0.01 s
sage: v = [int(a) for a in m]
sage: time v.sort() # no better (see below).
CPU times: user 0.02 s, sys: 0.00 s, total: 0.02 s
Wall time: 0.02 s
sage: v = numpy.array(m).astype(numpy.int64)
sage: time v.sort()
CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
Wall time: 0.00 s
sage: time m.sort()
CPU times: user 0.02 s, sys: 0.00 s, total: 0.02 s
Wall time: 0.02 s
However, sorting using an numpy int64 array seems to be much faster than sorting a list of GMP ints. So if we just use some straightforward sort on a Cython long long* we should get good results.
Unrelated: I noticed that n.divisors(...) doesn't have a sorted option. So maybe some code in integer.pyx should be slightly changed.
Yep. I plan to specialize Integer.divisors() and attempt to call that in the top-level divisors function. It will have a much faster sort.
I'm wondering though if the default should be sorted, or if one should use sorted(divisors(n))
if one wants them in order.
OK, here's another patch that should resolve all of the above issues. One of the doctests depends on #4534 (odd_part returning an integer rather than a rational).
I updated the patch, now its faster than pari.
There is a dependancy on #4564 for long long -> mpz_t.
Is it "faster than Magma" too? Could you do some timings?
Surprisingly, magma isn't super good at this problem. All timings on sage.math
sage: n = factorial(20)
sage: %timeit v = n.divisors()
100 loops, best of 3: 15.6 ms per loop
sage: pari_n = gp(n)
sage: %timeit v = pari_n.divisors()
10 loops, best of 3: 57.2 ms per loop
sage: magma_n = magma(n)
sage: %timeit v = magma_n.Divisors()
10 loops, best of 3: 43.5 ms per loop
sage: n = factorial(31) // 2^26
sage: %timeit v = n.divisors()
10 loops, best of 3: 145 ms per loop
sage: pari_n = gp(n)
sage: %timeit v = pari_n.divisors()
10 loops, best of 3: 408 ms per loop
sage: magma_n = magma(n)
sage: %timeit v = magma_n.Divisors()
10 loops, best of 3: 684 ms per loop
Attachment: 4533-divisors-final.patch.gz
Another oops:
sage -t -long devel/sage/sage/combinat/species/species.py
**********************************************************************
File "/scratch/mabshoff/release-cycle/sage-3.2.1.alpha0/devel/sage-main/sage/combinat/species/species.py", line 292:
sage: G.isotype_generating_series().coefficients(5)
Exception raised:
Traceback (most recent call last):
File "/scratch/mabshoff/release-cycle/sage-3.2.1.alpha0/local/bin/ncadoctest.py", line 1231, in run_one_test
self.run_one_example(test, example, filename, compileflags)
File "/scratch/mabshoff/release-cycle/sage-3.2.1.alpha0/local/bin/sagedoctest.py", line 38, in run_one_example
OrigDocTestRunner.run_one_example(self, test, example, filename, compileflags)
File "/scratch/mabshoff/release-cycle/sage-3.2.1.alpha0/local/bin/ncadoctest.py", line 1172, in run_one_example
compileflags, 1) in test.globs
File "<doctest __main__.example_12[7]>", line 1, in <module>
G.isotype_generating_series().coefficients(Integer(5))###line 292:
sage: G.isotype_generating_series().coefficients(5)
File "/scratch/mabshoff/release-cycle/sage-3.2.1.alpha0/local/lib/python2.5/site-packages/sage/combinat/species/series.py", line 668, in coefficients
return [self.coefficient(i) for i in range(n)]
File "/scratch/mabshoff/release-cycle/sage-3.2.1.alpha0/local/lib/python2.5/site-packages/sage/combinat/species/series.py", line 830, in coefficient
return self._stream[n]
File "/scratch/mabshoff/release-cycle/sage-3.2.1.alpha0/local/lib/python2.5/site-packages/sage/combinat/species/stream.py", line 311, in __getitem__
self._list.append(self._gen.next())
File "/scratch/mabshoff/release-cycle/sage-3.2.1.alpha0/local/lib/python2.5/site-packages/sage/combinat/species/generating_series.py", line 428, in _ogs_gen
yield sum( self.coefficient(i).coefficients() )
File "/scratch/mabshoff/release-cycle/sage-3.2.1.alpha0/local/lib/python2.5/site-packages/sage/combinat/species/series.py", line 830, in coefficient
return self._stream[n]
File "/scratch/mabshoff/release-cycle/sage-3.2.1.alpha0/local/lib/python2.5/site-packages/sage/combinat/species/stream.py", line 311, in __getitem__
self._list.append(self._gen.next())
File "/scratch/mabshoff/release-cycle/sage-3.2.1.alpha0/local/lib/python2.5/site-packages/sage/combinat/species/generating_series.py", line 506, in _functorial_compose_gen
t = g._cycle_type(s)
File "/scratch/mabshoff/release-cycle/sage-3.2.1.alpha0/local/lib/python2.5/site-packages/sage/combinat/species/generating_series.py", line 530, in _cycle_type
for d in divisors(k):
File "/scratch/mabshoff/release-cycle/sage-3.2.1.alpha0/local/lib/python2.5/site-packages/sage/rings/arith.py", line 924, in divisors
n = integer_ring.ZZ(n) # we have specalized code for this case, make sure it gets used
NameError: global name 'integer_ring' is not defined
**********************************************************************
The problem pops up in a lot of places.
Cheers,
Michael
[11:56pm] craigcitro: mabshoff: there's an easy fix for the problem in #4533
[11:56pm] craigcitro: it's a rebase issue
[11:56pm] mabshoff_: mk
[11:56pm] mabshoff_: if you can rebase the patch it would be cool.
[11:56pm] craigcitro: integer_ring. occurs in the patch
[11:56pm] craigcitro: just delete that.
[11:56pm] mabshoff_: I didn't even look yet
[11:56pm] mabshoff_: ?
[11:56pm] mabshoff_: Let me see
[11:56pm] craigcitro: it refers to
[11:56pm] craigcitro: integer_ring.ZZ
[11:57pm] craigcitro: it should just be ZZ
[11:57pm] craigcitro: it's something i cleaned up when i did stuff to arith.py
[11:57pm] craigcitro: but rob was using 3.1.4 when he wrote the patch
[11:57pm] craigcitro: and it's hitting a case we didn't think to try once it was rebased, probably
[11:57pm] mabshoff_: mk
[11:58pm] craigcitro: actually, i also just spotted a minor issue in that file
[11:58pm] craigcitro: where memory is only freed in one case ...
[11:58pm] mabshoff_: ok:
[11:58pm] mabshoff_: + if R in [int, long]:
[11:58pm] mabshoff_: + n = ZZ(n) # we have specalized code for this case, make sure it gets used
[11:58pm] mabshoff_: + try:
[11:58pm] craigcitro: yep
[11:58pm] mabshoff_: Well, post a new patch and I will test it.
[11:58pm] craigcitro: that should fix the problem you were hitting
[11:59pm] craigcitro: it's just outdenting one line
[11:59pm] craigcitro: the sage_free(ptr)
[11:59pm] craigcitro: should be one level back
[11:59pm] craigcitro: oh, wait
[11:59pm] craigcitro: nevermind
[11:59pm] craigcitro: he already takes care of it somewhere else in the code.
[12:00am] craigcitro: so that's not an issue.
[12:00am] mabshoff_: Arrg, I just deleted the edited patch
[12:00am] craigcitro: doh
[12:02am] mabshoff_: Well, I can fix it.
Merged the edited patch in Sage 3.2.1.alpha0
There's tons of room for optimization for the divisors function in sage/rings/arith.py. This should probably be generalized to do more than integers, with a specialized divisors method on Integer.
CC: @craigcitro
Component: algebra
Issue created by migration from https://trac.sagemath.org/ticket/4533