2

I'm trying to write a small script that will look at the first term of an expression and determine whether it is positive or negative, then print a + or - in front of that expression, accordingly; however, I'm having a bit of trouble writing it in such a way that it reliably extracts the first term of the expression.

I have been experimenting with part and args. I have been leaning towards args because I haven't found any way to determine the "depth" of parts for an arbitrary expression (i.e. I'm not sure how one can determine whether to use, e.g. part(expr,1) or part(expr,1,1) or part(expr, 1,1,1) etc.).

The issue with args is that, e.g.

declare(cos, posfun)$
args(-2*cos(x));
    > [2 cos(x)]

i.e. the negative is dropped, presumably due to the lisp representation of the expression (we get the same result from part(-2*cos(x),1); moreover, part(-2*cos(x),2) "falls off the end" -- it seems part simply can't see the -).

By contrast,

args(-2*cos(x)+x);
    > [x, -2cos(x) ]

as expected.

Regardless of whether or not this is the desired behaviour for these functions, I was hoping to find some way to get around it so that I can have a function that would have the following bevaiour:

addOp(x) > ["+", x]
addOp(-x) > ["-", x]

addOp(1+2*x+x^2) > ["+", 1+2*x+x^2]
addOp(-2+2*x+x^2) > ["-", 2+2*x+x^2] /* NB: only the first term is scaled by -1, not the entire expression */

addOp(cos(...)) > ["+", cos(...)]
addOp(-2x*cos(...)) > ["-", 2x*cos(x) ]

I also tried using the op function along with a known number; however, the internal representation of negative numbers means that something like op(1-3*cos(x)) returns +.

This one has had me stumped for a while, so any suggestions would be greatly appreciated.

Rax Adaam
  • 790
  • 3
  • 11

2 Answers2

2

Here's my first try. It seems to mostly work the way you describe except for %o11 because the -2 gets moved from the beginning to the end.

(%i1) f(e):= if atom(e) then ["+", e]
 else if op(e) = "-" then ["-", -e]
 elseif op(e) = "+" then [f(first(e)), rest(e)]
 else ["+", e];
(%o1) f(e) := if atom(e) then ["+", e] else (if op(e) = "-" then ["-", - e]
                  elseif op(e) = "+" then [f(first(e)), rest(e)] else ["+", e])
(%i2) f(x);
(%o2)                               [+, x]
(%i3) f(-x);
(%o3)                               [-, x]
(%i4) f(-2*x);
(%o4)                              [-, 2 x]
(%i5) f(-2*cos(x));
(%o5)                            [-, 2 cos(x)]
(%i6) f(1-2*cos(x));
(%o6)                        [[+, 1], - 2 cos(x)]
(%i7) f(-1+2*cos(x));
(%o7)                        [[+, 2 cos(x)], - 1]
(%i8) f(-1-2*cos(x));
(%o8)                        [[-, 2 cos(x)], - 1]
(%i9) f(a*b+c*d-e*f*g);
(%o9)                       [[-, e f g], c d + a b]
(%i10) f(1+2*x+x^2);
                                    2
(%o10)                        [[+, x ], 2 x + 1]
(%i11) f(-2+2*x+x^2);
                                    2
(%o11)                        [[+, x ], 2 x - 2]
(%i12) f(cos(a*b-c));
(%o12)                         [+, cos(c - a b)]
(%i13) f(-2*cos(x-y*z));
(%o13)                        [-, 2 cos(y z - x)]
(%i14) f(-2*x*cos(b-c));
(%o14)                        [-, 2 cos(c - b) x]
(%i15) -2+2*x+x^2;
                                  2
(%o15)                           x  + 2 x - 2
(%i16) f(-2 + 2*x - x^2);
                                    2
(%o16)                        [[-, x ], 2 x - 2]
(%i17) -2 + 2*x - x^2;
                                   2
(%o17)                         (- x ) + 2 x - 2
(%i18) f(a-b);
(%o18)                           [[+, a], - b]
(%i19) f(b-a);
(%o19)                           [[+, b], - a]

The business about op(e) = "-" is that stuff like -2*cos(x) gets reorganized into -(2*cos(x)) before args works on it (although I think inpart disables that behavior or modifies it).

EDIT: Take 2. atom(-2) returns true, so -2 is caught by the first case in the previous definition. Here's another try, where negative numbers are distinguished from other atoms.

f(e):= 
if atom(e)
 then (if numberp(e) and e < 0 then ["-", -e] else ["+", e])
 else if op(e) = "-" then ["-", -e]
 elseif op(e) = "+" then [f(first(e)), rest(e)]
 else ["+", e];

I didn't try this code but maybe you can say whether it works.

Robert Dodier
  • 16,905
  • 2
  • 31
  • 48
  • Wow! Thank you Robert. Looks like `%i7` gets swapped like `%i11`; I wonder if `negsumdispflag` might take care of that. I'll give it a shot. I'll also have a closer look at `inpart`. Hope all is well on your end! – Rax Adaam Mar 03 '21 at 18:19
  • 1
    Great, glad to hear it helps. About `inflag`, (oops, sorry, I meant to say `inflag` instead of `inpart`, although that's also in the vicinity) the effect is to make expression-hacking functions work on the internal representation instead of what's displayed. In a way the internal representation is more consistent, so maybe that is helpful in some way. – Robert Dodier Mar 03 '21 at 18:55
  • I tried `powerdisp:true` to address cases like `-2+2*x+x^2`; however, it returns `[ [+, -2], 2x+x^2 ]`. Played around with `inflag` and `negsumdispflag` a bit, but can't seem to get it to catch the -2. My thought was to add a `numberp` check prior to the `atom` -- does this make sense, or might there be a more direct / robust way of catching this case? – Rax Adaam Mar 03 '21 at 20:11
  • Hm, interesting. I've updated the answer with another try. – Robert Dodier Mar 03 '21 at 20:16
  • Yes - I was testing when you replied (as soon as noticed `atom(-2)` was true. It seems to behave correctly. Just as a last check re: the approach, the issue I'm trying to resolve is getting the correct operator to display when combining coloured terms such as `colour(a, 0) + colour(-b, 2)` (where it is not known whether b> 0 or <0, in advance). I tried using `subst`, as well as a few other approaches, but this seemed the most promising (so far), given my current knowledge of Maxima. – Rax Adaam Mar 03 '21 at 20:25
0

Addenda to Robert's answer, in case it may be of use to others. The following are sample scripts for extracting the values and reassembling them (after some transformation).

NB instead of "+" and "-", for the purpose of reassembling, I redefined the function to return 1 or -1, instead.

Robert's Function (Modified):

f(e):=  
        if numberp(e) 
        then  if e >= 0
              then [1, e]
              else [-1, -e]
        else  if atom(e) 
              then [1, e]
              else  if op(e) = "-" 
                    then [-1, -e]
                    else  if op(e) = "+" 
                          then [f(first(e)), rest(e)]
                          else [1, e];

Extract the Sign

Takes an expression of the form [+/-1, e] or [ [+/-1, e], r] and returns the +/-1.

fExtSg(expr):=
  block([expr:expr],
  if listp(expr[1])
  then expr[1][1]
  else expr[1]);

Extract the Term

Takes an expression of the form [+/-1, e] or [ [+/-1, e], r] and returns e.

fExtTerm(expr):=
  block([expr:expr],
  if listp(expr[1])
  then expr[1][2]
  else expr[2]
  );

Extract Rest

Takes an expression of the form [+/-1, e] or [ [+/-1, e], r] and returns r or 0.

fExtRest(expr):=
  block([expr:expr],
  if listp(expr[1])
  then expr[2]
  else 0
  );

Example of breaking the expression apart & reassembling:

Test:[-1+c,c-1,-b,3,-7*sin(x)-10*cos(x), -7*sin(x)+10*cos(x), 7*sin(x)-10*cos(x)];

/* apply `f` to all terms of the test list */
fTest: makelist(f(Test[i]), i, length(Test));

/* collect the signs into a list */
First_Term_Signs: makelist(fExtSg(fTest[i]), i, length(Test));

/* collect the first terms */
First_Terms: makelist(fExtTerm(fTest[i]), i, length(Test));

/* collect the rest */
Rests: makelist(fExtRest(fTest[i]), i, length(Test));

/* recombine and compare to original list, to ensure proper functioning */
reassembled_test: makelist(First_Term_Signs[i]*First_Terms[i] + Rests[i], i, length(Test)); 

NB -1 + c and c - 1 will both be handled the same by Maxima, the resulting "reassembled" version will match the output returned by Maxima...

Rax Adaam
  • 790
  • 3
  • 11