Menu ▾ ▴

#5722 plot2d premature evaluation

None
open
nobody
plot2d (32)
5
2 hours ago
2026-10-26
No

Normally, plot2d has no problem dealing with errors:

plot2d('(if sin(i)<0 then i else log(-i)),[i,0,10])
expr: 0; plot2d('(if sin(i)<0 then i else log(expr)),[i,0,10])

They warn that they encountered log(0), but still display a plot with gaps.

But these produce no graphs at all:

plot2d('(if sin(i)<0 then i else log(0)),[i,0,10])
plot2d('(if sin(i)<0 then i else log(i-i)),[i,0,10])

Apparently plot2d is pre-evaluating/simplifying part of the expression.

Another problematic case (which was actually my original problem).

This produces a nice, helpful graph:

expr:sin(1)^2+cos(1)^2-1;
sc(x):=block([fpprec:floor(x)],float(log(1e-100+abs(bfloat(expr)))/log(10.0)));
plot2d(sc,[i,5,100]);

But this gives nonsense:

expr:sin(1)^2+cos(1)^2-1;
sc(x):=block([fpprec:floor(x)],float(log(1e-100+abs(bfloat(''expr)))/log(10.0)));
plot2d(sc,[i,5,100]);

Apparently it is evaluating the expr using float() in the global context.

Maxima 5.49.0 SBCL 2.5.1

2 Attachments

Discussion

  • David Scherfgen

    David Scherfgen - 6 days ago

    This isn't related to plot2d:

    (%i1) '(if x then 42 else log(0));
    log: encountered log(0).
     -- an error. To debug this try: debugmode(true);
    

    The expression is simplified, and the log simplifier then encounters the zero argument.

    Is this a bug?

     

    Last edit: David Scherfgen 6 days ago
    • Stavros Macrakis

      Good point! Silly of me to overlook that!

      Indeed, '(if false then 1/0) gives an error because the arguments to if are
      being simplified even if the expression is not evaluated. This feels wrong,
      even if I understand what is happening. I would be happier if it simplified
      to '(if false then 1/0). That should, I think, be a straightforward
      change to simp-mcond -- if a clause causes an error while simplifying,
      just keep it as is.

      The other case given in the example is worse. It seems that the simplifier
      is called on arguments to block, which means that any bindings which
      affect simplification are ignored, for example, block([fpprec:21], 1+1b-20-1) simplifies to block([fpprec:100], 0.0b0) rather than block([fpprec:21], 1b-20). Similarly for, say, dotassoc. This contrasts with lambda,
      which doesn't look at its contents at all:
      '(lambda([fpprec],1+1b-20-1)(21)), which I'd think should denote
      exactly the same as the block expression.

      Much more broadly than plotting, this means that we can't manipulate Maxima
      code as data.

       

      Last edit: Robert Dodier 2 hours ago
      • David Scherfgen

        David Scherfgen - 5 days ago

        This is what Claude has to say:

        The ''expr case isn't caused by block simplification. plot2d calls sc with numer:true bound (coerce-maxima-function-or-maxima-lambda in plot.lisp). With ''expr the body contains sin(1) and cos(1) literally, and numer turns them into doubles before bfloat sees them. With bfloat(expr) the stored value of expr comes back unchanged, so numer never touches it. The fpprec binding works fine, since := stores the body unsimplified and block binds before evaluating it.

        /* sc defined with ''expr */
        sc(20);                       /* -99.99999999999999, same as with expr */
        block([numer:true], sc(20));  /* -15.954589770191001 */
        

        Workaround: block([numer:false, fpprec:floor(x)], ...). quad_qags, find_root etc. share this code, so dropping the numer binding would change them too. The plot2d documentation should at least mention it.

        Your point about block as data is right, but it's a separate issue. block has no simplifier of its own, so simpargs simplifies its body in the global environment. lambda, := and ::= have simplifiers that leave the body alone. Hence '(block([fpprec:21], 1+1b-20-1)) gives block([fpprec:21], 0.0b0), and define(g(), '(block(...))) defines a different function than g() := block(...). The same goes for (a, b), do, if and errcatch.

        Giving block a simplifier like simplambda fixes it. The only new failure in the core and share tests is rtest_pytranslate problem 41, whose expected output relies on the quoted block being simplified (c+1+2+3 becomes 6+c). Does that sound like the right direction?

        simp-mcond only exists in share/contrib/boolsimp. Core if goes through simpargs like block, but leaving its arguments alone is trickier, because an if with an undecidable condition relies on the simplifier to tidy its branches.

         
        • Robert Dodier

          Robert Dodier - 2 hours ago

          The big picture here is that both if and plot2d (via COERCE-FLOAT-FUN) have a certain amount of do-what-I-mean heuristics built into them. At this point I think I am inclined to open separate tickets for if and plot2d / COERCE-FLOAT-FUN and close this one.

          A few random observations.

          (1) Stavros, it would help if you say what's the expected output from the plotting example with the function sc.

          (2) Does makelist produce the expected result, e.g. makelist(sc(i), i, 10, 20)? (I ask because makelist also has nonstandard evaluation and I wonder what it does with this example.)

          (3) The nonstandard evaluation and simplification of if implements some choices I made last time I worked on that stuff (maybe about 20 years ago). At the time I tried to make it work better as an unevaluated conditional. (It was already nonstandard, I just adjusted stuff.) Whatever I did (plus whatever came before and after) is certainly open to revision. I think I left some comments in the code about it.

          (4) COERCE-FLOAT-FUN tries pretty hard to get to a float. I guess the problem with sc in plot2d is that it's trying slightly too hard. We will have to look into details.

          (5) Incidentally, there is also a COERCE-BFLOAT-FUN, I think it's called from bigfloat versions of some numerical functions.

          (6) I find the stuff from Claude about ''expr pretty frustrating. Just a long meandering text blob; I find myself wondering what, exactly, is the point. The problems that we're talking about are the consequences of design decisions -- any solution has to start there. It's not useful to start in a lengthy digression about something or the other and then wander further off the track. I know you, David, are finding Claude helpful, that's great. I just don't have the patience to try to get it back on track. /rant over.

           

Log in to post a comment.