8

Please consider:

Clear[x]
expr = Sum[x^i, {i, 15}]^30;

CoefficientList[expr, x]; // Timing
Coefficient[Expand@expr, x, 234]; // Timing
Coefficient[expr, x, 234]; // Timing
{0.047, Null}
{0.047, Null}
{4.93, Null}

Help states:

Coefficient works whether or not expr is explicitly given in expanded form.

Is there a reason why Coefficient needs to be so slow in the last case?

Mr.Wizard
  • 24,179
  • 5
  • 44
  • 125
  • 1
    Perhaps `Coefficient`'s algorithm trades off speed for space, to be able to work on expressions with an extremely long expanded form? BTW your computer is 4.5 times faster than mine. – Szabolcs Nov 23 '11 at 14:33
  • @Szabolcs I suppose that makes sense; I'll try to test it. I could hope for a more intelligent method selection process if that's the case. If you are using version 8, can you also try the test in version 7? I have a suspicion that at least some things are slower in 8. – Mr.Wizard Nov 23 '11 at 14:42
  • I don't have a v7 install handy now .. – Szabolcs Nov 23 '11 at 14:43
  • @Szabolcs It's also 4 times slower on mine (v8), so I would guess it's a version difference. – acl Nov 23 '11 at 14:51
  • @Szabolcs I can confirm your hypothesis, at lest in that `Coefficient` is far more memory efficient on `(1 + x)^50000`. Is there anything reasonable I can do to make a generalized function that calls `Coefficient` faster? Is there some kind of semi-expanded form, or `Method` option that that would give me a balance between these options? – Mr.Wizard Nov 23 '11 at 14:53
  • With larger expression situation is even worse. With `expr = Sum[x^i, {i, 30}]^20;` there's a factor of 670 between them. I don't notice much difference as far as memory usage is concerned, but the system monitor isn't very sensitive. – Sjoerd C. de Vries Nov 23 '11 at 14:54
  • @Sjoerd thanks for testing. Please try with sample: `(1 + x)^50000` or similar. – Mr.Wizard Nov 23 '11 at 14:57
  • Did you see the other weird version difference (between 8.0.x and 8.0.4), [here](http://dsp.stackexchange.com/questions/374/river-detection-in-text/682#comment1545_682)? Belisarius's solution doesn't work as-is on 8.0.4, apparently due to a change in `Radon[]` – Szabolcs Nov 23 '11 at 15:01
  • Got 20% to 30% slower results on 8.0.4 when compared with 7.0.1 (but still half the speed of Mr.Wizard...) – P. Fonseca Nov 23 '11 at 19:10

4 Answers4

12

Here is a hack which may enable your code to be fast, but I don't guarantee it to always work correctly:

ClearAll[withFastCoefficient];
SetAttributes[withFastCoefficient, HoldFirst];
withFastCoefficient[code_] :=
   Block[{Binomial},
     Binomial[x_, y_] := 10 /; ! FreeQ[Stack[_][[-6]], Coefficient];
     code]

Using it, we get:

In[58]:= withFastCoefficient[Coefficient[expr,x,234]]//Timing
Out[58]= {0.172,3116518719381876183528738595379210}

The idea is that, Coefficient is using Binomial internally to estimate the number of terms, and then expands (calls Expand) if the number of terms is less than 1000, which you can check by using Trace[..., TraceInternal->True]. And when it does not expand, it computes lots of sums of large coefficient lists dominated by zeros, and this is apparently slower than expanding, for a range of expressions. What I do is to fool Binomial into returning a small number (10), but I also tried to make it such that it will only affect the Binomial called internally by Coefficient:

In[67]:= withFastCoefficient[f[Binomial[7,4]]Coefficient[expr,x,234]]
Out[67]= 3116518719381876183528738595379210 f[35] 

I can not however guarantee that there are no examples where Binomial somewhere else in the code will be computed incorrectly.

EDIT

Of course, a safer alternative that always exists is to redefine Coefficient using the Villegas - Gayley trick, expanding an expression inside it and calling it again:

Unprotect[Coefficient];
Module[{inCoefficient},
   Coefficient[expr_, args__] :=
      Block[{inCoefficient = True},
         Coefficient[Expand[expr], args]] /; ! TrueQ[inCoefficient]
];
Protect[Coefficient];

EDIT 2

My first suggestion had an advantage that we defined a macro which modified the properties of functions locally, but disadvantage that it was unsafe. My second suggestion is safer but modifies Coefficient globally, so it will always expand until we remove that definition. We can have the best of both worlds with the help of Internal`InheritedBlock, which creates a local copy of a given function. Here is the code:

ClearAll[withExpandingCoefficient];
SetAttributes[withExpandingCoefficient, HoldFirst];
withExpandingCoefficient[code_] :=
   Module[{inCoefficient},
      Internal`InheritedBlock[{Coefficient},
         Unprotect[Coefficient];
         Coefficient[expr_, args__] :=
            Block[{inCoefficient = True},
               Coefficient[Expand[expr], args]] /; ! TrueQ[inCoefficient];
         Protect[Coefficient];
         code
      ]
   ];

The usage is similar to the first case:

In[92]:= withExpandingCoefficient[Coefficient[expr,x,234]]//Timing
Out[92]= {0.156,3116518719381876183528738595379210}

The main Coefficient function remains unaffected however:

In[93]:= DownValues[Coefficient]
Out[93]= {}
Community
  • 1
  • 1
Leonid Shifrin
  • 22,449
  • 4
  • 68
  • 100
  • +1 for the reverse engineering work and analysis, but isn't this really the same as manually expanding first, but unsafe? Or is it still more memory efficient than that (i.e. does the internal decision to expand or not to expand refer to the whole expression, or parts only)? – Szabolcs Nov 23 '11 at 16:04
  • @Szabolcs I just edited to add the other possibility (expansion) before seeing your comment. It is probably the same memory - wise, and when Coefficient expands, it expands all expression (as far as I can tell). My first solution is more for an illustration. I will add the second one based on Gayley - Villegas trick shortly. – Leonid Shifrin Nov 23 '11 at 16:06
10

Coefficient will not expand unless it deems it absolutely necessary to do so. This does indeed avoid memory explosions. I believe it has been this way since version 3 (I think I was working on it around 1995 or so).

It can also be faster to avoid expansion. Here is a simple example.

In[28]:= expr = Sum[x^i + y^j + z^k, {i, 15}, {j, 10}, {k, 20}]^20;

In[29]:= Coefficient[expr, x, 234]; // Timing

Out[29]= {0.81, Null}

But this next appears to hang in version 8, and takes at least a half minute in the development Mathematica (where Expand was changed).

Coefficient[Expand[expr], x, 234]; // Timing

Possibly some heuristics should be added to look for univariates that will not explode. Does not seem like a high priority item though.

Daniel Lichtblau

rcollyer
  • 10,475
  • 4
  • 48
  • 75
Daniel Lichtblau
  • 6,854
  • 1
  • 23
  • 30
9
expr = Sum[x^i, {i, 15}]^30; 

scoeff[ex_, var_, n_] /; PolynomialQ[ex, var] := 
   ex + O[var]^(n + 1) /. 
    Verbatim[SeriesData][_, 0, c_List, nmin_, nmax_, 1] :> 
     If[nmax - nmin != Length[c], 0, c[[-1]]]; 

Timing[scoeff[expr, x, 234]]

seems quite fast, too.

rcollyer
  • 10,475
  • 4
  • 48
  • 75
Rolf Mertig
  • 1,360
  • 8
  • 22
  • +1. It is actually several times faster for larger expressions, than solutions based on expanding an expression. Moreover, it is dramatically faster for smaller-index coefficients, since it effectively seems to expand only the part of expression up to a specified term (power). – Leonid Shifrin Nov 23 '11 at 17:27
  • SeriesData is really very nicely implemented in Mathematica. So anything one can do with SeriesData is blazingly fast (even faster than FORM (http://www.nikhef.nl/~form/)). – Rolf Mertig Nov 23 '11 at 17:44
  • Rolf Mertig, I am compelled to accept this answer, as it is concise and efficacious. However, I do not really understand it. Would you please explain it in the simplest terms possible? – Mr.Wizard Nov 25 '11 at 05:58
  • Specifically I do not understand: `ex + O[var]^(n + 1)` – Mr.Wizard Nov 25 '11 at 06:06
  • It is just a Taylor expansion which makes sense here since the higher powers in x are not needed. The speedup comes from the fact that SeriesData is implemented efficiently in Mathematica. See also: http://reference.wolfram.com/mathematica/ref/O.html – Rolf Mertig Nov 25 '11 at 12:53
  • Thank you. Pardon me for un-Accept-ing your answer, but I think I found an easier method, inspired by yours. I will wait for peer review on that answer. – Mr.Wizard Nov 25 '11 at 17:37
1

After some experimentation following Rolf Mertig's answer, this appears to be the fastest method on the type of expression such as Sum[x^i, {i, 15}]^30:

SeriesCoefficient[expr, {x, 0, 234}]
Mr.Wizard
  • 24,179
  • 5
  • 44
  • 125
  • Note that this answers somewhat different question from what you originally asked: you asked about the reasons why `Coefficient` is slow (emphasis on `Coefficient` and "why slow"), not what are possible faster substitutes for it. – Leonid Shifrin Nov 29 '11 at 20:55
  • @Leonid very good point. I had forgotten that after reading the various answers. I suppose Daniel's reply is most direct; should I accept that? Also, what do you think of this method? Are there any obvious problems with it? – Mr.Wizard Nov 29 '11 at 21:03
  • Sorry, I can't advise on which answer you accept - this is entirely up to you. Daniel's answer is very good, brief and to the point. As to the method - I don't see obvious problems, but alas have no time right now to play with it. – Leonid Shifrin Nov 29 '11 at 21:15
  • @Leonid You are sounding quite diplomatic. Okay. – Mr.Wizard Nov 29 '11 at 21:17