Comment #15 on issue 2607 by asmeurer: as_numer_denom() is too slow
http://code.google.com/p/sympy/issues/detail?id=2607

Here's what I've got so far:

def as_numer_denom(self):
    return self.as_numer_denom_orig()

def as_numer_denom_orig(self):
    numers, denoms = [],[]
    for n,d in [f.as_numer_denom() for f in self.args]:
        numers.append(n)
        denoms.append(d)
    r = xrange(len(numers))
    return Add(*[Mul(*(denoms[:i]+[numers[i]]+denoms[i+1:]))
                 for i in r]), Mul(*denoms)
def as_numer_denom1(self):
    """
    Combine pairwise.
    """
    numers, denoms = zip(*[f.as_numer_denom() for f in self.args])
    num = [numers[0]]
    dom = denoms[0]
    for n, d in zip(numers[1:], denoms[1:]):
        for i in xrange(len(num)):
            num[i] *= d
        num.append(n*dom)
        dom *= d
    return Add(*num), dom

def as_numer_denom2(self):
    """
    Combine pariwise; don't Mul until the end.
    """
    numers, denoms = zip(*[f.as_numer_denom() for f in self.args])
    num = [[numers[0]]]
    dom = [denoms[0]]
    for n, d in zip(numers[1:], denoms[1:]):
        for i in xrange(len(num)):
            num[i].append(d)
        num.append([n] + dom)
        dom.append(d)
    return Add(*(Mul(*n) for n in num)), Mul(*dom)

def as_numer_denom3(self):
    """
    Combine pairwise; don't distribute the denominator.
    """
    numers, denoms = zip(*[f.as_numer_denom() for f in self.args])
    num = numers[0]
    dom = denoms[0]
    for n, d in zip(numers[1:], denoms[1:]):
        num *= d
        num += n*dom
        dom *= d
    return num, dom

as_numer_denom() is the current implementation and as_numer_denom3() does what I suggested in comment 14.

This is super fast for the expression I gave above where the denominators are all just 1, but it's slow for actual expressions.

This seems to be wrong. It's slower for smaller expressions, but it must be asymptotically faster, because it's way faster for large expressions. For example:

In [9]: numers, denoms = zip(*((Symbol('n%d'%i),Symbol('d%d'%i)) for i in xrange(10)))

In [10]: a = Add(*(n/d for n, d in zip(numers, denoms)))

In [11]: a.as_numer_denom3()
Out[11]:
(d₀⋅d₁⋅d₂⋅d₃⋅d₄⋅d₅⋅d₆⋅d₇⋅d₈⋅n₉ + d₉⋅(d₀⋅(d₁⋅d₂⋅d₃⋅d₅⋅d₆⋅d₇⋅d₈⋅n₄ + d₄⋅(d₁⋅d₂⋅d₃⋅d₆⋅d₇⋅d₈⋅n₅ + d₅⋅(d₁⋅d₂⋅d₃⋅d₇⋅d₈⋅n₆ + d₆⋅(d₁⋅d₃⋅d₇⋅d₈⋅n₂ + d₂⋅(d₁⋅d₃⋅d₇⋅n₈ + d₈⋅(d₁⋅d₇⋅n₃ + d₃⋅(d₁⋅ n₇ + d₇⋅n₁))))))) + d₁⋅d₂⋅d₃⋅d₄⋅d₅⋅d₆⋅d₇⋅d₈⋅n₀), d₀⋅d₁⋅d₂⋅d₃⋅d₄⋅d₅⋅d₆⋅d₇⋅d₈⋅d₉)

In [12]: %timeit a.as_numer_denom()
1000 loops, best of 3: 939 us per loop

In [13]: %timeit a.as_numer_denom1()
100 loops, best of 3: 1.39 ms per loop

In [14]: %timeit a.as_numer_denom2()
1000 loops, best of 3: 2.27 ms per loop

In [15]: %timeit a.as_numer_denom3()
100 loops, best of 3: 2.19 ms per loop

In [16]: numers, denoms = zip(*((Symbol('n%d'%i),Symbol('d%d'%i)) for i in xrange(1000)))

In [17]: a = Add(*(n/d for n, d in zip(numers, denoms)))

In [18]: %timeit a.as_numer_denom()
1 loops, best of 3: 5.79 s per loop

In [19]: %timeit a.as_numer_denom1()
(oo; I had to interrupt it after a long time)

In [20]: %timeit a.as_numer_denom2()
1 loops, best of 3: 6 s per loop

In [21]: %timeit a.as_numer_denom3()
1 loops, best of 3: 121 ms per loop

I'm hoping I can discover a fast way to created unnested expressions, as the expressions like the one in Out[11] are far slower when used by other things (like the printer for example). I might say to call expand on it, if expand weren't so slow also. Also, being highly nested, they tend to create "recursion depth exceeded" errors (e.g., try printing the output of [21]; indeed, I can't even figure out how to expand it without getting that error).


--
You received this message because you are subscribed to the Google Groups 
"sympy-issues" 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/sympy-issues?hl=en.

Reply via email to