an interesting eval example

The following example arrived in my email inbox a few weeks ago. It spurred a short but lively thread of discussion amongst some Maple developers.

I thought that it was interesting enough to post here. I'll hold off on giving my own opinion right away, because I'm curious to read what other MaplePrimes members might write about it.

> q := (6*((1/3)*a-1/9))/(36*a-116+12*sqrt(12*a^3-3*a^2-54*a+93))^(1/3);
                                   6 (a/3 - 1/9)
            q := --------------------------------------------------
                                       3      2             1/2 1/3
                 (36 a - 116 + 12 (12 a  - 3 a  - 54 a + 93)   )


> eval(q, a = 1/3); # this is the contentious part
                                       0


> eval(numer(q),a=1/3);
                                       0


> eval(denom(q),a=1/3);
                           /            1/2  1/2\(1/3)
                           |       4 676    9   |
                         3 |-104 + -------------|
                           \             3      /


> radnormal(%);
                                       0

Dave Linder
Mathematical Software, Maplesoft

Comments

some more

series(q,a=1/3,2);

                       1     (2/3)   (1/3)    /    1\
                       -- 108      13      + O|a - -|
                       54                     \    3/
map(evalf,%);
                                           /    1\
                           0.9874986897 + O|a - -|
                                           \    3/

limit(q,a=1/3);
                                  undefined
MultiSeries:-limit(q,a=1/3);
                                  undefined

MultiSeries:-series(q,a=1/3,2);
map(radnormal,%);
map(evalf,%);


                                 2                   /    1\
                 - ------------------------------ + O|a - -|
                                            (1/3)    \    3/
                     /   2     (1/2)  (1/2)\                
                   3 |- --- 676      9     |                
                     \  507                /                
                       1     (2/3)   (1/3)    /    1\
                       - (-4)      13      + O|a - -|
                       6                      \    3/
                                                    /    1\
                  -0.4937493449 + 0.8551989515 I + O|a - -|
                                                    \    3/

This MultiSeries is the latest algolib version on Maple 12.

PS Interesting: This plot 

dq:=denom(q);
plot([Re(dq),Im(dq)],a=0..1/2);

may explain why different series expansions produce different results, and the limits give "undefined" if they use a L'Hospital rule or something equivalent, as the derivative of the denominator change at a=1/3.

PS2 These other plots seem to show that the real and imaginary parts of the denominator on the complex plane are piecewise linear, with the planes meeting in a kind of "origami" pattern at a=1/3:

plots:-complexplot3d([Re(dq),.4],a=0-0.2*I..0.5+0.2*I,axes=boxed); plots:-complexplot3d([Im(dq),.2],a=0-0.2*I..0.5+0.2*I,axes=boxed);

(one) point of interest

While the behaviour of the expression around a=1/3 may be interesting in its own right, the bit that we discussed was the behaviour of eval() at just that given point.

More specifically, is it a "bug"? And if so then how serious is it? Should the burden be on the user to recognize the issue and work around it? Is documentation of the potential problem "good enough"? Is speed more important than correctness here? And what might be done about resolving the class of such problems directly?

Dave Linder
Mathematical Software, Maplesoft

point and neighborhood

The behavior in the neighborhood shows that there is no limit for q as a->1/3. There are lateral/directional limits:
 

limit(q,a=1/3,right);
                             1     (2/3)   (1/3)
                             -- 108      13     
                             54                 
limit(q,a=1/3,left);
                           1        (2/3)   (1/3)
                           -- (-108)      13     
                           54                    

Hence, it seems to me that the current result of 'eval' is a bug, and the result should be "undefined", consistent with the output of 'limit'.

Yes. I think that it is a rather serious bug. It would be desirable that, at least, a warning  about potential problems were issued in case that algorithmically it were not simple to decide for "undefined". Correctness (or at least not-incorrecteness) is priority. Of how much time savings are you thinking 1s?, 1 min? Probably it would take for the user much more time than that to realize of the bug and trace it back.

I think that 'eval' should check for 'limit' in case of doubt (apparently it is not doing that).

PS I think that you mean this statement in ?eval

Since eval does pointwise evaluation, eval cannot be used to evaluate an expression at a singularity. Use limit instead.
 

Well, somehow 'eval' should realize that there is a singularity to, at least, issue a warning. I do not see how it can be done without information about the neighborhood.

The background of this issue, is, as I have expressed in a previous post, that 'eval' is an undefined command: part of its functionality is about mathematics and part about data structures. It cannot do both things well. So, its needs to be splitted into two distinct commands.

 

alec's picture

More simple example

I have a more simple example,

f:=(a-2)/(a-8^(1/3));
                                  a - 2
                           f := ----------
                                     (1/3)
                                a - 8

eval(f,a=2);

                                  0

It is clearly wrong, and even the limit in this case is 1, which Maple gives correctly,

limit(f,a=2);
                                  1

A way to avoid that would be to evaluate fractional powers of integers - at least in the way similar to sqrt (which is also, by the way, far from ideal, but at least such examples would require much larger numbers.)

Another way may be in doing evalf and comparing results. In this example,

eval(evalf(f),a=2);
                           Float(undefined)

as well as in the original example

eval(evalf(q),a=1/3);
                           Float(undefined)

which would also show the error.

For exact integer calculations the correctness should be the main concern - not speed. Anyway, if I did that, I would write it in C (or directly in assembler) and put such things, together with sqrt in the kernel (as Mathematica does, by the way.)

By the way, this example gives another proof that 0=1 in Maple,

is(f=radnormal(f));
                                 true
eval(f=radnormal(f),a=2);
                                0 = 1

Alec

Axel Vogt's picture

Alec's example (a-2)/(a-8^(1/3))

I am not that sure, that Maple behaves wrong (and the following
may apply partially also to the initial posted question).

For me it is how to interprete the expression and what is done on
it afterwards.

At first that expression for me is a rational polynomial or more
precisely it represents an equivalence class in the field R(a) of
fractions of R[a], the polynomial ring in a (do not care for R).

And as thus it equals (the equivalence class of ) 1, no doubt.

However I do distinguish between a polynomial (or rational poly)
and the induced function (which is evaluation), say x^2+x and 0
is not the same in characteristic 2 or 1/x is fine in IR(x), but
not as function on IR.

What is done by using unapply? I am not trained in CS, so lambda
calculus is not my thing (thus find no help at the help page) - 
but from Algebra I would have the desire, that this does *not*
depend on the representant of the equivalence class.

But Maple does not check it (and in general would possibly not be
able to do that), it gives just a 'functional operator' as the
help says (what ever is meant by that ...).

So a priori it has no need to check for 'singularities'. May be
it should (since in some cases it would transform the expression,
say automatic simplification.

This goes together with "eval can not be used at a singularity".

However F:=a->(a-2)/(a-8^(1/3)) and F(2)=0 for me is a bug. Yes?
Well, actually in that form F is only defined except a=2 and we
use an continuation to all IR - and we *expect* the system to use
the continous extension. So it is a bit questionable ... at least
in a formal sense. 

But a sad trap. On the other hand in general extending is not
trivial (mostly needs Analysis as in the case above).


So any formulation, which implicitely makes Maple to manipulate
the original expression is not guaranteed to be equivalent as a
point evaluation due to singularities - do I get that right?


How about letting the system spit out conditions or restrictions
or 'reformulations'?

eval vs evaluate fn

I have never gotten into the habit of using eval. One of the things that I immediately valued about Maple was how it treated functions, and so I have always used function evaluation instead of eval. As I have read Mapleprimes, I have always been surprised at how many people prefer eval.

In this case, set up q as a proc, and there is no problem:

 

q := a->(6*((1/3)*a-1/9))/(36*a-116+12*sqrt(12*a^3-3*a^2-54*a+93))^(1/3);

q(1/3) ;

Error, (in q) numeric exception: division by zero

>limit(q(a),a=1/3);

undefined

So as long as q as a function works properly, I could care less about eval. But it would be nice if the two perspective were consistent.

 

 

alec's picture

However

However,

F:=a->(a-2)/(a-8^(1/3));
                                     a - 2
                         F := a -> ----------
                                        (1/3)
                                   a - 8

F(2);

                                  0

as well as

F(2.);
                                  0.

Alec

unapply

Notice the following discrepancy:

 q := (6*((1/3)*a-1/9))/(36*a-116+12*sqrt(12*a^3-3*a^2-54*a+93))^(1/3);
                                   6 (a/3 - 1/9)
            q := --------------------------------------------------
                                       3      2             1/2 1/3
                 (36 a - 116 + 12 (12 a  - 3 a  - 54 a + 93)   )
f1 := unapply(q,a);                                                         
                                     6 (1/3 a - 1/9)
         f1 := a -> --------------------------------------------------
                                          3      2             1/2 1/3
                    (36 a - 116 + 12 (12 a  - 3 a  - 54 a + 93)   )

f2 := a -> (6*((1/3)*a-1/9))/(36*a-116+12*sqrt(12*a^3-3*a^2-54*a+93))^(1/3);
                                     6 (1/3 a - 1/9)
        f2 := a -> ---------------------------------------------------
                                             3      2              1/3
                   (36 a - 116 + 12 sqrt(12 a  - 3 a  - 54 a + 93))
f1(1/3);
                                       0

f2(1/3);
Error, (in f2) numeric exception: division by zero

Using unapply caused a conversion from sqrt to an exponent of 1/2. 

acer's picture

just to be clear

I'm not sure whether Joe meant "going that route, with eventual use of unapply" as opposed to "use of unapply". I suspect that he meant the former, because he knows Maple very well. But it can be made explicitly clear, for the benefit of other readers.

It is not the application of unapply, in itself, which causes the conversion of sqrt to an exponent of 1/2. Running these examples above, in the Standard GUI, with 2D output makes output containing either sqrt or ^(1/2) appear with the mathematical surd symbol. But underneath, they are different.

For symbolic `a` the input sqrt(a^2) will result in output of the form a^(1/2). This symbolic evaluation is the point at which conversion from sqrt to power 1/2 takes place. Subsequent unapply w.r.t. symbol `a` isn't where that conversion happens. Although, clearly, going the route of symbolic-evaluation followed by unapply together entail the conversion.

> restart:
>
> z := sqrt(a^2); # the "loss" is here
                                        2 1/2
                                 z := (a )

> lprint(%);
(a^2)^(1/2)
>
> f := unapply(z,a);
                                           2 1/2
                               f := a -> (a )

> lprint(%);
a -> (a^2)^(1/2)
>
> f(4);
                                       1/2
                                     16

The above may be contrasted with what happens when the sqrt() is embedded within a procedure. In that case, sqrt() is not preempted by the evaluation at nonnumeric symbol `a`.

> g := a -> sqrt(a);
                               g := a -> sqrt(a)

> lprint(%);
a -> sqrt(a)
>
> g(4);
                                       2

None of this is anything new, of course. It's old news. And it's what underlies the problem of the original example. (I recall trying to explain once before, on here.)

Now, what could be done about this state of affairs? Could `^` expressions be simplified at uniquification time? Could the eval() routine substitute calls of the form `^`(...,1/integer) with calls to sqrt() or root(), in the case that the evaluation point is numeric?

acer

alec's picture

Expressions vs. functions

I can't say for everybody, but I usually choose whether to use an expression or a function depending on the task. If there is a need of evaluating it at some point(s), then the function is more convenient (less typing). Otherwise (that means in most cases) an expression is more convenient (also less typing). That sometimes leads to either using eval or unapply.

If q in this example was a result of some calculation, like integration, or differentiation, for example, then a normal way of further evaluation of it at a=1/3 or at any other point would be either through eval or unapply (instead of retyping it as a function.) In both cases Maple gives a wrong result.

Alec

Axel Vogt's picture

Not an error (due to documentation), but ...

Not an error due to documentation, which says:

  "Since eval does pointwise evaluation, eval cannot be used to
   evaluate an expression at a singularity. Use limit instead."

That's an ugly trap. In which sense ever one wants to consider q
(as expression to comute a number or in some algebraic structure).

So it meets the specification - it does not meet user's needs.


To have it simplier I used eval(q,a = a/3); eval(%,a=a+1)/2*3;
to have a=0 as the problematic point:

  Q := a/(12*a-104+4*(4*a^3+9*a^2-156*a+676)^(1/2))^(1/3);

The singularity is easy to find using singular: it is just a=0.

And the limit is seen not to exist (@Jakubi: take a wider range
-16 ... 10 to see, that Re or Im(denom) is not piecewise linear).


I am a fan of save results (i.e. Maple should check, whether eval
can be used) - but they may last long, so some 'lame and fast' eval
then would be helpfull.

NB: for the above subs(a=0,Q) or subs(a=0.0,Q) behaves similar.

Funny, that rationalize(Q) gives it as algebraic/a^2 and thus it
gives no problems.


Besides Alec's nice example I have some, where eval is not allowed
due to the documentation, but I see no easy way to find it out:

  J:= x -> Int( ln( (x-1)^2 + 4*x*sin(theta)^2 ), theta=0..Pi/2 );

  theQ:= x/J(x);

Now I want to check singularities by singular(theQ,x), which gives
{x = infinity}, {x = -infinity}. So the following should be correct
eval(theQ,x=0), eval(theQ,x=0.0); subs(x=0,theQ), subs(x=0.0,theQ);
They all return zeros.

However the following shows J to be identical 0 for -1 <= x <= 1:

  op(J(x)), method = _d01ajc: Int(%);
  plot([Re(J(x)), Im(J(x))], x= - 2..2, numpoints=5, thickness=2,
    color=[red,blue]);

I filed that function once and IIRC it was given by Robert Israel.

It also shows how dangerous it may be to use functions from some
expressions without thinking about the domain of definitions: for
the q posted here one should have in mind, that 3rd root is 'only'
a function on positive reals (which Alex is aware of, for sure;
Edited: however what is if I do not want to see that as function,
but as rational polynomial, say for x/x ?).
Just my 2 cents ... and thx - it is interesting!


BTW: how all that is done in other CAS (if comparable)?
alec's picture

In Mathematica

Mathematica 6.03 does this correctly, both for evaluating and in the function form,

In[1]:= q = (6 ((1/3) a - 1/9))/(36 a - 116 + 
     12 Sqrt[12 a^3 - 3 a^2 - 54 a + 93])^(1/3)

Out[1]= (6 (-(1/9) + a/3))/(-116 + 36 a + 
  12 Sqrt[93 - 54 a - 3 a^2 + 12 a^3])^(1/3)

In[2]:= q /. a -> 1/3

During evaluation of In[2]:= Power::infy: Infinite expression \
1/0^(1/3) encountered. >>

During evaluation of In[2]:= \[Infinity]::indet: Indeterminate \
expression 0 ComplexInfinity encountered. >>

Out[2]= Indeterminate

In[3]:= q = (6 ((1/3) # - 1/9))/(36 # - 116 + 
      12 Sqrt[12*#^3 - 3 #^2 - 54 # + 93])^(1/3) & 

Out[3]= (
 6 (#1/3 - 1/9))/(36 #1 - 116 + 
   12 Sqrt[12 #1^3 - 3 #1^2 - 54 #1 + 93])^(1/3) &

In[4]:= q[1/3]

During evaluation of In[4]:= Power::infy: Infinite expression 1/0 \
encountered. >>

During evaluation of In[4]:= \[Infinity]::indet: Indeterminate \
expression 0 ComplexInfinity encountered. >>

Out[4]= Indeterminate

In my example, in the function form the result is the same as above, and entered as an expression, it outputs 1, so it gives 1 after any substitution.

Alec

alec's picture

In SAGE and Maxima

SAGE gives an error returned from Maxima,

sage: a=var('a')
sage: q = (6*((1/3)*a-1/9))/(36*a-116+12*sqrt(12*a^3-3*a^2-54*a+93))^(1/3)
sage: q(1/3)
---------------------------------------------------------------------------
             Traceback (most recent call last)
... [long output skipped]
Error executing code in Maxima
CODE:
	(0/1) / (((-104/1) + ((12) * (sqrt(676/9)))) ^ (1/3));
Maxima ERROR:
	
Division by 0

Alec

JacquesC's picture

My reactions

  1. If Mathematica and Maxima both complain about this, it is difficult to justify (other than through appeals to implementation details) Maple's behaviour.
  2. Maple's inconsistency shown above by Joe Riel between eval and unapply+function evaluation is most definitely a bug.  They should be consistent with each other.
  3. That expression evaluation and proc evaluation is also inconsistent (as shown by Alex Smith) is also problematic.  It should be very rare that this happens.
  4. It is interesting to note that, by default, both Mathematica and Maxima use stronger normalizers for radical expressions than Maple does.  Why is Maple's so weak?!?

Of course, I am quite sure that one could find more complicated examples where Mathematica and Maxima would fail too.  It would be interesting to see exactly how they fail in those cases.  In other words, are all systems buggy in this fashion, with the only point of difference being how easy it is to demonstrate the problem?  If so, then in some sense the solution is simple: make the default zero-tester in Maple more powerful, to move the boundary to a spot similar to other systems.

acer's picture

configurable?

Jacques, do you think that the zero-tester in use by eval should be configurable?

acer

JacquesC's picture

Unsure

Honestly, I am unsure.  There are pros and cons, and I can't really see a winning argument for either side.  My current feeling is that:

  1. The default evaluation environment should have a much strong zero-tester and normalizer
  2. It should be possible to use other evaluation environments (at least locally) with different zero-test and normalizer.  Including turning off 'simpl' altogether.

Note that I don't mean Normalizer and Testzero -- mostly because these are not used pervasively enough!  Changing these has much less effect than desired.  One (partial) implementation strategy would be to have many more calls to Normalizer and Testzero in Maple's library and kernel.  But I am really not sure if that is actually the best way to go!

I consider it a bug.

If one uses the root command instead of 1/3 as the exponent to guarantee that you get the principle root, you get an immediate simplification for

F:= (a-2)/(a-root(8,3));

to 1.  The function form

f2 := a -> (a-2)/(a-root(8,3));

works perfectly at 2, but gives 1 for any variable input.

 

With the first example, if one uses simplify on the denominator for a=1/3 one gets 0.  Shouldn't a check for a zero denominator always be done?

q := (6*((1/3)*a-1/9))/(36*a-116+12*sqrt(12*a^3-3*a^2-54*a+93))^(1/3);
simplify(eval(denom(q),a=1/3));

 

Finally, a question:  Is the basic problem in Maple's evaluation of this expressions that it does not check for a 0 denominator?
 

Axel Vogt's picture

and now?

Dave, so what is your opinion - and those of your colleagues?

an opinion

I said that I would give my opinion on this. So, what follows is that: just my own personal opinion.

Let's call the functionality evalat, just to distinguish it from 1-argument eval.

I think that the behaviour in the posted example shows something that could (and should) be improved.

One scenario that concerns me here is that in which the calling of evalat is made deep inside some routine in the shipped Maple Library. That's quite different from cases where the user calls it interactively (directly) at the top-level. I'm thinking of cases where there is no chance for the user to intercede and choose to do some different analysis of the expression at or near the point.

And, as I only found out only a few days ago, this very example was such a case. The user received an answer for a complicated computation that turned out to be wrong. This (longstanding, expert) user was fortunately able to recognize that the result was wrong, and was able to trace the problem back to this particular evaluation at a point, which was being done within some Library routine. So there was no clean and simple workaround -- no immediate way to make the Library routine take some other approach.

As a Maple user, I would be prepared to take a small performance penalty if evalat could be improved here. If one wanted to use a super fast but mathematically unsafe evaluator then one could make do with subs, which is more intended for substitution into a data structure. But eval is supposed to be the smarter, mathematical safer evaluator.

And the improvement should be centralized. I don't buy a line of argument which says that individual Library routines all ought to be more careful in their use of evalat. The evalat functionality is used very widely within the Maple Library. If Maple's going to be improved to be stronger in its zero detection when evaluating expressions at points, for mathematical computation, then that should be done centrally.

Maple is not finished. No computer algebra system will ever be perfect. But the competition will improve, and thus so must Maple. I think that we ought to be able to seriously revisit this kind of functionality at least once every few years. It's in the class of issues for which -- while perfection is unattainable -- incremental, practical improvements are good.

This problem should not be impossible to cover adequately. Perhaps radicals could be dealt with at object simplification/uniquification time. Eg. 27^(1/2) -> 3*3^(1/2) automatically. Having evalat revert to substituting `^` by root and sqrt (as shown here) would be prohibitively expensive.

An extensible, user-customizable solution might be cheapest, if the default behaviour were not changed. That's because the system would only have to check something simple like a flag. And the user could choose how hard the system ought to work at zero testing. But then, how helpful would it be, if the default behaviour were left as it is?

Dave Linder
Mathematical Software, Maplesoft

smarter mathematical evaluator

Indeed, there should be a "smarter, mathematical safer evaluator". The problem is that 'eval' is packed with many unrelated functionalities. As you call it the single argument 'eval' on one side and the two argument "evalat" functionality on the other. But this "evalat" is yet a mixture of data structure and mathematical functionalies. I.e. it makes both mathematical sensible evaluations and mathematical "absurd" evaluations when it operates on the "hidden" structure of the expressions.

E.g., currently:

eval(x^4+1,1=27^(1/2));
                             4   1/2
                            x  27    + 27

and for this kind of functionality, I do not see any advantage of making here, automatically, the transformation 27^(1/2) -> 3*3^(1/2).

So, my point of view is that a distinct specific mathematical smart "evalat" command is needed. And it is this one which needs user options for harder testing.

JacquesC's picture

Usefulness

As you've pointed out, it all comes down to 'usefulness', and more precisely, how useful is an evalat function which is sometimes silently wrong?  Of course, if the problem was that simple, it would have been fixed long ago.  The other question is, how is useful is an evalat function which is really really slow?

Personally, I am greatly inspired by one of the principal design decisions behind the Chez Scheme compiler, namely that "Lesser used operations must pay their own way" (see Kent Dybvig's wonderful paper on the historical development of Chez Scheme, or if you're in a hurry, the slides).  Another way to say this is that lesser used operations must not have any cost when they are not used.

In Maple, this is frequently not the case - new features make older (and much more common) features slower.  So adding new (core) features is a non-trivial decision.  If many routines, most notably evalat, were structured such that the cost of execution was directly proportional to the features used in the actual call [rather than being proportional to all the features that the function supports], then this whole problem would go away!  evalat would be as expensive (or cheap) as necessary to get the job done. 

In my mind, the system should work as hard as zero-testing as the expression at hand requires.  What the system should most definitely not do is to always work as hard at zero-testing as the potentially most complicated expression expressible in Maple requires.  And for too many Maple routines, one has to pay the cost of the full power, instead of the sliding cost of "just enough" power.

Usefulness

I like to think that there is some path to an evalat function which is not "really, really" slow and which is also significantly stronger than at present.

Dave Linder
Mathematical Software, Maplesoft

JacquesC's picture

I agree

I believe that the ideas which I listed in my previous post are the key ideas needed for that path.  They are really quite profound design idea which are quite fruitful once you 'get it'. 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
}