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
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 54Hence, 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.
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); 0It is clearly wrong, and even the limit in this case is 1, which Maple gives correctly,
limit(f,a=2); 1A 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 = 1Alec
Alec's example (a-2)/(a-8^(1/3))
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.
However
However,
F:=a->(a-2)/(a-8^(1/3)); a - 2 F := a -> ---------- (1/3) a - 8 F(2); 0as well as
F(2.); 0.Alec
unapply
Notice the following discrepancy:
Using unapply caused a conversion from sqrt to an exponent of 1/2.
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 16The 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); 2None 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
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
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 ?).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]= IndeterminateIn 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
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 0Alec
My reactions
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.
configurable?
Jacques, do you think that the zero-tester in use by eval should be configurable?
acer
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:
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
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?
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 + 27and 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.
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
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'.