Menu ▾ ▴

#4033 {a,rat(a)} is different from {rat(a),a}

None
open
set (5)
5
2026-10-03
2022-10-26
No
test: [ {a,rat(a)} , {rat(a),a} ] => [ {a}, {a} ]

is(test[1]=test[2]) => false

apply('set,test) => { {a}, {a} }
union(test[1],test[2]) => {a} << a
union(test[2],test[1]) => {a} << rat(a)

But...
   is(a=rat(a)) => true

?print(test)$

((MLIST SIMP)
   (($SET SIMP) $A)
   (($SET SIMP) ((MRAT SIMP ($A) (#:A160)) (#:A160 1 1) . 1))
   )

Not sure this is a bug.
In any case, not a high-priority one.

Related

Bugs: #4033

Discussion

  • Robert Dodier

    Robert Dodier - 2026-10-02

    I think it's a bug; "=" for sets should be defined in terms of "=" for elements, so if elements are equivalent under "=", then so are the sets, I think. It appears that "=" for the sets is not punting to "=" for the elements as I would expect. I don't know how that would happen -- I seem to recall ALIKE1 for nonatomic expressions calls ALIKE1 for the operator and for each pair of arguments.

    I haven't looked at ALIKE1 for some time and I haven't further investigated this bug, so I may be wildly mistaken about the whole thing, if so I apologize in advance.

     
    • Stavros Macrakis

      I am on my phone only for another week, so can't look into this in detail.
      But if you trace alike1, you'll see that rat(a} has been disrepped by the
      time it gets to alike1 in is/=, setify, and for that matter sort. So it
      can't distinguish them.

      On Fri, Oct 2, 2026, 21:22 Robert Dodier via Maxima-bugs maxima-bugs@lists.sourceforge.net wrote:

      I think it's a bug; "=" for sets should be defined in terms of "=" for
      elements, so if elements are equivalent under "=", then so are the sets, I
      think. It appears that "=" for the sets is not punting to "=" for the
      elements as I would expect. I don't know how that would happen -- I seem to
      recall ALIKE1 for nonatomic expressions calls ALIKE1 for the operator and
      for each pair of arguments.

      I haven't looked at ALIKE1 for some time and I haven't further
      investigated this bug, so I may be wildly mistaken about the whole thing,
      if so I apologize in advance.


      [bugs:#4033] https://sourceforge.net/p/maxima/bugs/4033/ {a,rat(a)} is
      different from {rat(a),a}

      Status: open
      Group: None
      Labels: set
      Created: Wed Oct 26, 2022 05:30 PM UTC by Stavros Macrakis
      Last Updated: Wed Oct 26, 2022 07:55 PM UTC
      Owner: Barton Willis

      test: [ {a,rat(a)} , {rat(a),a} ] => [ {a}, {a} ]
      is(test[1]=test[2]) => false
      apply('set,test) => { {a}, {a} }union(test[1],test[2]) => {a} << aunion(test[2],test[1]) => {a} << rat(a)
      But... is(a=rat(a)) => true

      ?print(test)$
      ((MLIST SIMP) (($SET SIMP) $A) (($SET SIMP) ((MRAT SIMP ($A) (#:A160)) (#:A160 1 1) . 1)) )

      Not sure this is a bug.
      In any case, not a high-priority one.


      Sent from sourceforge.net because maxima-bugs@lists.sourceforge.net is
      subscribed to https://sourceforge.net/p/maxima/bugs/

      To unsubscribe from further messages, a project admin can change settings
      at https://sourceforge.net/p/maxima/admin/bugs/options. Or, if this is a
      mailing list, you can unsubscribe from the mailing list.


      Maxima-bugs mailing list
      Maxima-bugs@lists.sourceforge.net
      https://lists.sourceforge.net/lists/listinfo/maxima-bugs

       

      Related

      Bugs: #4033

      • David Scherfgen

        David Scherfgen - 2026-10-02

        I let Claude have a look at this. Stavros is right for a top-level CRE: LIKE, which is/= uses, converts it before calling ALIKE1. Inside a set or list, though, the MRAT reaches ALIKE1 untouched. Tracing is({a} = {rat(a)}) shows (ALIKE1 $A <MRAT>) returning NIL, since ALIKE1 never treats an MRAT as alike to a general expression. sort can't tell them apart because orderlessp converts at every depth.

        A small patch makes LIKE compare nested CRE forms by value, so = for sets follows = for elements, as Robert expected. ALIKE1 itself stays unchanged, since rat and taylor code rely on it. The test suite passes. Patch to follow.

         
      • David Scherfgen

        David Scherfgen - 2026-10-03

        I have a patch for this. With it, is(x = y), the set functions, member, delete and assoc treat a CRE form like its general form at any depth. So {a, rat(a)} and {rat(a), a} are equal, is([a] = [rat(a)]) and member([a], [[rat(a)]]) are true, and assoc(a, [rat(a) = 1]) returns 1. The test suite passes.

        But there's still the issue of ordering: great doesn't order a CRE form nested inside another expression like its general form. So elementp, adjoin and disjoin can go wrong on set members containing CRE forms, and sums can sort their terms differently:

        (%i1) elementp([c], {[a], [rat(b)], [c]});
        (%o1) false
        (%i2) adjoin([b], {[rat(a)]});
        (%o2)/R/ {[b], [a]}
        (%i3) elementp([rat(c)], {[a], [rat(b)], [c]});
        Maxima encountered a Lisp error:
        
         GREAT: internal error: unexpected MRAT argument
        (%i4) is(f([rat(a)]) + f([b]) = f([a]) + f([b]));
        (%o4) false
        

        Shall we change great to order a CRE form like its general form? This fixes all of the above at once. The change is small, the test suite passes, results without CRE forms are unchanged and there's no measurable slowdown, but it touches the core ordering function.

         
        • Stavros Macrakis

          How do you define great so that sort([a, rat(a), a]) produces a
          predictable result?

          How do you distinguish rat(a+b,a) from rat(a+b,b)? They display
          differently, but disrep to the same thing.

          I'd think that is(a=rat(a)) should be false, just as is(0=0.0) is false,
          and T[a] should be distinct from T[rat(a)].

          On Sat, Oct 3, 2026, 07:43 David Scherfgen via Maxima-bugs maxima-bugs@lists.sourceforge.net wrote:

          I have a patch for this. With it, is(x = y), the set functions, member,
          delete and assoc treat a CRE form like its general form at any depth. So {a,
          rat(a)} and {rat(a), a} are equal, is([a] = [rat(a)]) and member([a],
          [[rat(a)]]) are true, and assoc(a, [rat(a) = 1]) returns 1. The test
          suite passes.

          But there's still the issue of ordering: great doesn't order a CRE form
          nested inside another expression like its general form. So elementp,
          adjoin and disjoin can go wrong on set members containing CRE forms, and
          sums can sort their terms differently:

          (%i1) elementp([c], {[a], [rat(b)], [c]});(%o1) false(%i2) adjoin([b], {[rat(a)]});(%o2)/R/ {[b], [a]}(%i3) elementp([rat(c)], {[a], [rat(b)], [c]});Maxima encountered a Lisp error:
          GREAT: internal error: unexpected MRAT argument(%i4) is(f([rat(a)]) + f([b]) = f([a]) + f([b]));(%o4) false

          Shall we change great to order a CRE form like its general form? This
          fixes all of the above at once. The change is small, the test suite passes,
          results without CRE forms are unchanged and there's no measurable slowdown,
          but it touches the core ordering function.


          [bugs:#4033] https://sourceforge.net/p/maxima/bugs/4033/ {a,rat(a)} is
          different from {rat(a),a}

          Status: open
          Group: None
          Labels: set
          Created: Wed Oct 26, 2022 05:30 PM UTC by Stavros Macrakis
          Last Updated: Fri Oct 02, 2026 08:56 PM UTC
          Owner: Barton Willis

          test: [ {a,rat(a)} , {rat(a),a} ] => [ {a}, {a} ]
          is(test[1]=test[2]) => false
          apply('set,test) => { {a}, {a} }union(test[1],test[2]) => {a} << aunion(test[2],test[1]) => {a} << rat(a)
          But... is(a=rat(a)) => true

          ?print(test)$
          ((MLIST SIMP) (($SET SIMP) $A) (($SET SIMP) ((MRAT SIMP ($A) (#:A160)) (#:A160 1 1) . 1)) )

          Not sure this is a bug.
          In any case, not a high-priority one.


          Sent from sourceforge.net because maxima-bugs@lists.sourceforge.net is
          subscribed to https://sourceforge.net/p/maxima/bugs/

          To unsubscribe from further messages, a project admin can change settings
          at https://sourceforge.net/p/maxima/admin/bugs/options. Or, if this is a
          mailing list, you can unsubscribe from the mailing list.


          Maxima-bugs mailing list
          Maxima-bugs@lists.sourceforge.net
          https://lists.sourceforge.net/lists/listinfo/maxima-bugs

           

          Related

          Bugs: #4033

          • David Scherfgen

            David Scherfgen - 2026-10-03

            Behavior on current Maxima, before any patch:

            • sort with its default order first converts the elements to general form and then uses a stable sort. So sort([a, rat(a), a]) returns [a, a, a], all in general form, and a change to great wouldn't affect it. In great itself, a and rat(a) would simply tie, which is how orderlessp already treats them.
            • rat(a+b, a) and rat(a+b, b) are already treated as equal, also inside lists and sets: is(rat(a+b, a) = rat(a+b, b)) and is([rat(a+b, a)] = [rat(a+b, b)]) are true, and {rat(a+b, a), rat(a+b, b)} has one member. ALIKE1 compares two CRE forms by their general forms. The patch doesn't change this.
            • is(a = rat(a)) is true, and the manual's introduction to sets relies on it: "Set members x and y are equal provided is(x = y) evaluates to true. Thus rat(x) and x are equal as set members", with {x, rat(x)} giving {x}. rtestnset tests this, for example with set(x, rat(x), x, x, x) and elementp(rat(x), set(x)). Also, f(rat(a)) simplifies to f(a).
            • T[rat(a)] : 1 is an error ("hash function: cannot hash a special expression"), so hashed arrays never mix the two.

            So current Maxima treats a CRE form as another representation of the same expression. The bug is that = follows this design only at the top level. The patch makes it hold at any depth.

            If we'd rather have is(a = rat(a)) be false, the consistent fix goes the other way. = and ALIKE1 would compare CRE forms structurally, which would also separate rat(a+b, a) from rat(a+b, b), and {a, rat(a)} would have two members. The manual and the tests above would change. That's a bigger change in behavior, but also a coherent one.

            Which way do we want?

             
            • Stavros Macrakis

              I am surprised about the cases you mention. I thought I remembered the opposite, and specifically I think Barton and I discussed this when we were implementing the set package. I may be remembering incorrectly, but maybe someone could run a git bisect to see if it changed at some point?

              Fundamentally, Maxima the programming language (independently of symbolic calculations) needs to have some primitive for checking whether two things are the same value (like equal in Lisp). This is different from whether they are the same object (eq in Lisp, which Maxima does not offer) or whether they have the same mathematical value (like equal in Maxima).

              The basic axiom is that a=b implies f(a)=f(b) for all f. The simplest violation using the disrep approach is rat(x+y,x) vs. rat(x+y,y), for f=string, which are different. A more elaborate example involves x+1, rat(x+1), taylor(x+1,x,0,1), and taylor(x+1,x,0,5). Should they all be =? Certainly they give different values for f=taylorinfo. I suppose you could excuse that as some sort of utility function that cheats by peeking at internal representations. But they actually behave differently in substantive ways: taylor(exp(x),x,0,5)-taylor(x,x,0,1) gives a different result from taylor(exp(x),x,0,5)-taylor(x,x,0,5), so the basic definition is violated for f=taylor.

              Different but mathematically equal CRE expressions sometimes disrep to the same thing (rat(a+b,a) vs. rat(a+b,b)) but in other cases they do not (rat(a*b+a,a) => (b+1)*a; rat(a*b+a,b) => a*b+a). That also seems problematic; how do we explain to the user that some things that look different are in fact =, but others are not?

              Now, we could define = as "equal under disrep" (with all the issues I mention above), but in that case we'll need a parallel set of functions (=, setify, sort, member, ...) which actually use real equal for cases where it matters (or do we have a global use_strict_equal -- yuck!). That seems like unnecessary complexity.

               
              • David Scherfgen

                David Scherfgen - 2026-10-03

                I let Claude check the history. It hasn't changed:

                • = has converted a top-level CRE form to general form at least since the oldest commit in the repository, the Maxima 5.4 sources (2000): LIKE is ALIKE1 after SPECREPCHECK.
                • The set package's original documentation, imported in 2003 as "written by Barton Willis and Stavros Macrakis", says "Set members x and y are equal provided is(x = y) evaluates to true. Thus rat(x) and x are equal as set members", with set(x, rat(x)) giving {x}. Its "Bugs" section blamed is([rat(a)]=[rat(a)]) being false on like, not on the set code.
                • Since then, orderlessp converts CRE forms at every depth (2010), ALIKE1 compares two CRE forms by value (2012), and the "Bugs" section was removed because its examples worked (2016).

                Your examples are all correct, and they all behave that way today, before any patch. string and taylorinfo distinguish values that are =, Taylor truncation matters in arithmetic, and rat(a*b+a, a) and rat(a*b+a, b) are not = while rat(a+b, a) and rat(a+b, b) are. The patch changes none of that. It only makes = treat a CRE form below the top level the way it already does at the top level.

                A strict = is coherent, but existing code relies on the current one. Claude prototyped two strict versions and ran every test file:

                • If a CRE form is never = to a general form, 7 test files crash with unbounded recursion, for example in nusum, whose helper clist recurses until 0 = p with p a CRE. gcdex divides by zero, raddenest stops denesting, 65 of 210 orthopoly tests fail, and the documented set behavior changes ({x, rat(x)}, subsetp({rat(x)}, {x})).
                • If CRE forms with different variable orders or truncation are also distinct, Taylor and Zeilberger tests fail as well.

                So going strict means auditing and fixing such code (but what about foreign code that relies on this?) and changing the manual.

                 
                • Stavros Macrakis

                  Thanks for doing all that work just to correct my memory!
                  I still think it's problematic that a=b does not imply f(a)=f(b) for all f, but pragmatically, I agree that staying with the status quo makes sense.

                   
                  • David Scherfgen

                    David Scherfgen - 2026-10-03

                    Well, it was Claude that did the digging.

                    Then shall I go ahead with the patch that makes =, member, delete, assoc and the set functions treat a CRE form like its general form at every depth instead of just at the top level?

                    I'd also change great the same way. That's the same rule applied to ordering, and it fixes elementp, adjoin, disjoin and the term order of sums at once. I'd also add a note to the documentation of = that a CRE form and its general form are =, while functions that see the representation, such as string and taylorinfo, can still tell them apart.

                     

Log in to post a comment.