Loren on the Art of MATLAB

July 8th, 2009

A Brief History of polyval

When I first started working at MathWorks, there were already a few functions in MATLAB for working with polynomials. One of these, polyval, for polynomial evaluation, used one algorithm, based on Horner's rule, known for its efficiency, especially suitable for embedded controller environments. After a customer wondered why the function took so long to evaluate a given polynomial for a scalar input value x, we updated polyval to use a faster algorithm for the scalar case, and that algorithm uses the function filter. I thought I'd discuss a few more details here.

Note: since I wrote this post (but before I published it) there was a discussion on the newsgroup about this same topic. Be sure to read the reply from John D'Errico.

Contents

Horner's Rule

Let's first look at the code for evaluating a polynomial for multiple values of input x. Here's the relevant portion of polyval, nc is the length of the vector representing the polynomial (one more than the polynomial degree).

dbtype polyval 62:67
62    % Use Horner's method for general case where X is an array.
63    y = zeros(siz_x, superiorfloat(x,p));
64    if nc>0, y(:) = p(1); end
65    for i=2:nc
66        y = x .* y + p(i);
67    end

Here's what a general polynomial looks like:

This equation can be rewritten like this,

and is the basis for evaluating the polynomial for an array x. Notice that the formula basically starts with the coefficient of the highest degree (named ) multiplied by x, then adds the next coefficient, multiplies that result by x again, adds the next coefficient, and so on, until reaching the final coefficient (the constant term, for ). In this formulation, we never need to explicitly create all the powers of x to evaluate the polynomial.

The Scalar Case, Using filter

Here's the code for the scalar evalation case using the function filter.

dbtype polyval 52:53
52        y = filter(1,[1 -x],p);
53        y = y(nc);

It may not, at first glance, appear relevant to polynomial evaluation. However, taking a close look at the mathematics reveals that it indeed can be useful. First, let's examine the filter formula. In this version, the coefficients for a have been normalized so a(1) = 1 (which is true for the inputs to filter you've just seen).

Now, let's try a small example. Suppose we want to evaluate the same third degree polynomial I used in another set of posts recently.

p = [1 0 1 -1];

which represents

When you call filter like this y = filter(b,a,x), the results are

Looking again at the polyval code, use these substitutions for the inputs to filter: b = 1, a = [1 -x], and x = p. We evaluate the filter for the values p, the polynomial coefficients! Let's do a little math to see why filter works. Following the formula for the output of the function filter, and assuming p(0) = 0, we can find the first output.

And here are the rest, going up to the final coefficient in our example.

It sure looks like the final answer here is the polynomial p evaluated at x.

How Close are the Algorithms?

Numerically, the two algorithms end up being essentially the same.

x = 0:0.001:1;
resultArray = polyval(p,x);
resultScalar = zeros(size(x));
for index = 1:numel(x)
    resultScalar(index) = polyval(p,x(index));
end
allequal = isequal(resultArray,resultScalar)
allequal =
     1

Identical!

Timing

I mentioned we made this change for speed. This particular code is tricky to check timing since, especially if the polynomial degree is small, the number of mathematic operations can be swamped by the timing mechanism. When computers were slower, and before we increased performance of for-loops in MATLAB, measuring the performance differences between these code alternatives was much easier than it is today.

Multiple Algorithms

Do you try to write your code as one size fits all, or do you create code in which you switch algorithms or implementations, such as we've seen in polyval, to account for different conditions such as array size? I'd love to hear more about your experiences with these ideas here.


Get the MATLAB code

Published with MATLAB® 7.8

4 Responses to “A Brief History of polyval”

  1. Daniel replied on :

    As a sometimes follower of Dougs mantra, that one should not optimize if it sacrifices readibility, I tend to very carefully avoid multiple algorithms that do the same thing. Not only does the code become complex, but one must also verify both implementations, and the algorithm which selects which one to use.

    Now for end-user programs which are properly verified, that may be alright, but in my lab, one faulty calculation at the wrong time costs way more than a little sluggishness here and there.

    –DA
    PS. I seem to have ended up on the spamers-list for this blog. Is that because I comment too much? :P DS

  2. Loren replied on :

    Daniel-

    Thanks for your thoughts. No spammers list, but the comments are moderated so I can weed out ones that are totally off-topic. Please keep on commenting!

    –Loren

  3. James Ross replied on :

    Loren,
    Good question! I tend to write one algorithm and try to make it solid - the right answer is far more important than execution time, especially with the speed of today’s PC’s.

    If I feel Matlab is taking too long to execute my routines, out comes the Profiler and I attack the big hitters until the execution time is back under control. Recently, a colleague and I were able to reduce algorithm run-time from 12.5 hours down to 2.5 hours with a handful of changes based on the Profiler! With a few additional changes based on how we used Simulink, the final execution time was 30 minutes.

    While I’ve always been a big proponent of optimization to minimize end-user pain, that type of work has to be weighed against robustness and the effort required to eke out that last bit of performance.

  4. Brian McIntosh replied on :

    Nice topic, Loren. A few years ago while working with “ln” near unity, I ginned up the following functions.

    function b = exp1(a)
    %EXP1 (exp(a)-1) function
    % does not use HP-48 naming convention
    % since expm is taken
    % for use when exp(a) is nearly one
    % should work for any matrix [a]

    % Author(s): B. McIntosh 2004/03/01: 2004/03/30
    % Private user

    rsf = size(a);
    a = reshape(a,1,prod(rsf));
    b = exp(a)-1;
    ii = find(abs(a) < 0.25);
    b(ii) = (1./cumprod(1:12))*cumprod(ones(12,1)*a(ii));
    b = reshape(b,rsf);
    %% end of function

    function b = lnp1(a)
    %LNP1 log(a+1) function
    % uses HP-48 naming convention
    % for use when (a) is nearly zero
    % should work for any matrix [a]

    % Author(s): B. McIntosh 2004/03/01: 2004/03/10: 2004/03/30
    % Private user

    rsf = size(a);
    a = reshape(a,1,prod(rsf));
    b = log(a+1);
    ii = find(abs(a) < 0.065);
    b(ii) = -(1./(1:12))*cumprod(-1*ones(12,1)*a(ii));
    b = reshape(b,rsf);
    %% end of function

Leave a Reply

Wrap code fragments inside <pre> tags, like this:

<pre class="code">
a = magic(3);
sum(a)
</pre>

If you have a "<" character in your code, either follow it with a space or replace it with "&lt;" (including the semicolon).


Loren Shure works on design of the MATLAB language at The MathWorks. She writes here about once a week on MATLAB programming and related topics.

  • Jun: I totally can not believe it, Loren. You are really helpful. Thank you so much, MATLAB master!
  • Loren: Wow folks- Always lots of interest when there’s a quickie to try out! I will only make 2 general...
  • Loren: Jun- ismember is your friend here: >> [aa,ind] = ismember(Array2,Arra y1) aa = 1 1 1 1 1 1 1 ind = 1 2 1 4 4 3...
  • Dan: I like the first way better than the second way. Combining the arrays into one and running any is nice, although...
  • James Myatt: How about I = (a == 0 | b == 0); a(I) = []; b(I) = [];
  • Tunc: Hello Loren, love your blog because of such inspiring and challenging comments to such ’small’...
  • Pekka Kumpulainen: Here is my tradeoff. I usually want to keep the original variables as they are most probably...
  • Iain: Followup: Of course, to allow NaNs (counting them as non-zero): mask = (a~=0) & (b~=0); The mask says “a...
  • Matt Fig: I would usually go with something like this: y = a&b; x = a(y); y = b(y); But I was surprised to find...
  • kk: c=all([a;b]) a(c) a(b)

These postings are the author's and don't necessarily represent the opinions of The MathWorks.