Skip to content

Simplification & ordering

Algebraic equivalence

Is ? Well, it depends! In particular you can easily derive the contradiction and using our rule To avoid problems like this we therefore have decided that

ATAlgEquiv((a^x)^y, a^(x*y)) = [0, ""]

If you are teaching rules of indices to students for the first time this might come as a surprise! If you would like STACK to implement this rule, then you need to also assume(a>0). This can be done in the feedback variables. This is a design decision and not a bug (and is recorded in the system unit tests)!

Note the Maxima function rootscontract which converts products of roots into roots of products.

Note that Maxima resists the transformation , which is not always correct. Instead, and when you know this will be correct, use radcan with radexpand:all. For example, ev(radcan((a^b)^c),radexpand:all,simp).

Ordering terms

Maxima chooses an order in which to write terms in an expression. By default, this will use reverse lexicographical order for simple sums, so that we have instead of . In elementary mathematics this looks a little odd! One way to overcome this is to use simplification below but another way is to alter the order in which expressions are transformed.

To alter the order in STACK you can use the Maxima commands orderless and ordergreat. To have you can use

ordergreat(a,b);

See Maxima's documentation for more details.

  1. Only one orderless or ordergreat command can be issued in any session. The last one encountered will be used and the others ignored.
  2. No warnings or errors are issued if more than one is encountered.
  3. The orderless or ordergreat command is executed first before any other commands. Therefore the argument names are literal atoms and you cannot use variable names.

As an example of the last point, consider the following in desktop maxima

p:a+b; x:a; ordergreat(x); p:a+b;

The output of the last line, as expected will be . However, if you put the above in the question variables then effectively you will have the following.

ordergreat(x); / Other stuff, including setting up error trapping for the execution of commands below. / p:a+b; x:a; p:a+b;

The output of the last line, as expected will be . STACK moves ordergreat to be executed first, and at that point you have no assigned x to be the atom a.

This is a limitation, especially in questions where you want to have a randomly generated variable name.

Logarithms to an arbitrary base

By default, Maxima does not provide logarithms to an arbitrary base. To overcome this, STACK provides a function lg for student entry.

  • lg(x) is log of to the base 10.
  • lg(x, a) is log of to the base .

STACK provides no simplification rules for these logarithms. To simplify you must transform back to natural logarithms.

For example (with simp:true or simp:false)

p:lg(27, 3)
q:ev(p, lg=logbasesimp)

results in p=lg(27, 3), and q=3.

The algebraic equivalence function algebraic_equivalence, and so anything upon which it depends, will automatically remove logarithms to other bases. This includes the answer tests as needed.

Selective simplification

The level of simplification performed by Maxima can be controlled by changing Maxima's global variable simp, e.g.

simp:true

When simp is set to false, no simplification is performed and Maxima is quite happy to deal with an expression such as without actually performing the addition. This is most useful for dealing with very elementary expressions, and for showing working.

This variable can be set at the question level using the options or for each Potential response tree.

When simp is set to false, you can evaluate an expression with simplification turned on by using ev(..., simp), for example:

simp:false;
a:ev(1+1,simp);

will give .

Within CASText (question text, general feedback, etc.)

Sometimes it is useful to control the level of simplification applied to expressions included within CASText using {@...@}. In particular, to show steps in working, it is often necessary to turn simplification off.

To selectively control simplification within CASText (including the general feedback), you can use the following methods:

  1. Set simp:false in the question options, or at the end of your question variables. That way all expressions in the CASText will be unsimplified, but you can use {@ev(...,simp)@} to simplify selectively.
  2. Use evaluation flags to control the level of simplification for an individual CAS expression, for example:
{@3/9,simp=false@}
  1. Use a define block to set the value of simp, e.g.
[[define simp="false"/]]
\({@3/9@} \neq {@1+1@}\)
[[define simp="true"/]]
\({@3/9@} \neq {@1+1@}\)

will produce followed by . 4. Switch simplification

{@(simp:false,3/9)@}

This command sets the value of simp for this expression, and all others which follow, much like the define block above.

Unary minus and simplification

There are still some problems with the unary minus, e.g. sometimes we get the display when we would actually like to always display as . This is a problem with the unary minus function -(x) as compared to binary infix subtraction a-b.

To reproduce this problem type in the following into a Maxima session:

simp:false;
p:y^3-2*y^2-8*y;

This displays the polynomial as follows.

y^3-2*y^2+(-8)*y

Notice the first subtraction is fine, but the second one is not. To understand this, we can view the internal tree structure of the expression by typing in

?print(p);
((MPLUS) ((MEXPT) $Y 3) ((MMINUS) ((MTIMES) 2 ((MEXPT) $Y 2))) ((MTIMES) ((MMINUS) 8) $Y))

In the structure of this expression the first negative coefficient is -(2*y^2) BUT the second is -(8)*y. This again is a crucial but subtle difference! To address this issue we have a function

unary_minus_sort(p);

which pulls "-" out the front in a specific situation: that of a product with a negative number at the front. The result here is the anticipated y^3-2*y^2-8*y.

Note that STACK's display functions automatically apply unary_minus_sort(...) to any expression being displayed.

Really insisting on printing the parentheses

Why does STACK (i.e. Maxima) not print out the parentheses? For example, try the following.

simp:false;
p1:(a+b)+c;
tex(p1);

The result is . Where have the parentheses gone? On the other hand p2:a+(b+c) is displayed as . Why are these displayed differently? Assuming simp:false and using Maxima's ?print command we can see the internal structure.

  • ?print(a+b+c) gives ((MPLUS) $A $B $C). This means we have the flattened (nary) sum of the three variables. This will always not have brackets.
  • ?print((a+b)+c) gives ((MPLUS) ((MPLUS) $A $B) $C). This is not yet flattened to an nary sum, but Maxima's tex routines suppress the parentheses, even with simp:false. This is part of the problem.
  • ?print(a+(b+c)) gives ((MPLUS) $A ((MPLUS) $B $C)). This is not yet flattened to an nary sum, and in this case it displayed as by Maxima's TeX function.

Note, this display problem is not a bug. Experts would interpret as not as . This is only a problem in teaching when we want to display (arguably not needed) parentheses. To solve this display problem STACK has an inert disp_parens function. All this function does is print round brackets (parentheses) around its argument.

For example, try the following.

simp:false;
p1:disp_parens(a+b)+c;
tex(p1);

The result is .

Parentheses can also be added to other expressions which, strictly speaking, do not need them. For example int(disp_parens(x-2),x) is displayed as .

It may be necessary to remove the disp_parens from an expression. STACK provides the function remove_disp_parens(ex) to remove this inert display function. Actually, this function is remarkably simple.

remove_disp_parens(ex) := ev(ex, disp_parens=lambda([ex2], ex2))$

The function disp_parens has no mathematical definition. It just changes the TeX output. The above function re-evaluates the expression, with this function equal to the identity function (lambda([ex2], ex2))). Giving disp_parens this mathematical definition effectively removes it.

Note that the answer tests do not remove the disp_parens function from a teacher's expression. Hence, a+b+c and `disp_parens(a+b)+c are not algebraically equivalent. Teachers who use these display functions must remove them before answer tests are applied. Students cannot use the disp_parens function. Indeed, currently a student's input of (a+b)+c is displayed as Maxima does without the brackets (yes, this might be considered a bug).

Selecting, and highlighting part of an expression

Like disp_parens, STACK provides a function disp_select which highlights part of an expression. All this function does is colour the argument red and underline it. For example 1+disp_select(x^2+2) is displayed as . Note, the combination of colour and the underline is because it is considered poor practice to use colour alone to convey meaning.

STACK provides the function remove_disp_select(ex) to remove this inert display function. The function remove_disp(ex) removes all inert display functions.

When creating feedback it is often useful to select, and highlight, part of an expression. STACK provides a function select(p1, ex) to do this. The select function traverses the expression tree for ex and when it encounters a sub-tree for which the predicate p1 is true it adds disp_select to the sub-tree and stops traversing any further down that sub-tree. While nested disp_select are possible (and will display multiple underlines: another reason for having underline) this particular function stops once p1 is true. You will need to build nested display by hand.

For example, to select all the integers in an expression you can use the predicate integerp and select(integerp, 1+x+0.5*x^2) gives .

It is possible to use any of the existing predicate functions, or to define your own function in the question variables.

The function select_apply(f1, ex) traverses the expression and when it encounters the disp_select function it applied the function f1 to that sub-tree of the expression. This allows for selective simplification/modification of highlighted sub-trees. For example,

simp:false;
p1:select(zeroMulp, (1-1)*x^2+0*x+1);
p2:select_apply(simplify, p1);
p3:select_apply(simplify, p1, false);

generates the following displayed expressions.

  • p1 displays as . We have selected all the parts for which the predicate zeroMulp is true. This is the predicate which checks if the rule is applicable. While the coefficient of is equivalent to zero, it is unsimplified and the predicate zeroMulp(1-1) is false. This sub-tree is not selected by this predicate.
  • p2 displays as . The displayed expression is subjected to the function simplify, and the displayed part replaced. The rest of the expression is unchanged. By default the disp_select is removed and so the result is not coloured and underlined.
  • p3 displays as . Notice the third, optional boolean, argument to select_apply in p3. This argument will decide whether to continue to display the disp_select display or remove it (now the function has been applied). The default is true, so here the red underline is not removed.

If you really insist on a kludge....

In some situations you may find you really do need to work at the display level, construct a string and display this to the student in Maxima. Please avoid doing this!

a:sin(x^2);
b:1+x^2;
f:sconcat("\\frac{",stack_disp(a,""),"}{",stack_disp(b,""),"}");

Then you can put in \({@f@}\) into one of the CASText fields. Note, you need to add LaTeX maths delimiters, because when the CAS returns a string the command {@f@} will just display the contents of the string without maths delimiters.

Tips for manipulating expressions

How do we do the following in Maxima? Try

q:(1-x)^a*(x-1);
q:ratsubst(z,1-x,q);
q:subst(z=1-x ,q);

How do we do the following in Maxima?

 factor(radcan((x-1)*(k*(x-1))^a))

Maxima's internal representation of an expression sometimes does not correspond with what you expect -- in that case, dispform may help to bring it into the form you expect. For example, the output of solve in the following code shows the in the denominator as which gives unnatural-looking output when a value is substituted in -- this is fixed by using dispform and substituting into that variants instead.

simp:true;
eqn:b = 1/(6*a+3);
ta1: expand(rhs(solve(eqn,a)[1]));
dispta1:dispform(ta1);
simp:false;
subst(2,b,ta1);
subst(2,b,dispta1);

Creating sequences and series

One problem is that makelist needs simplification. To create sequences/series, try something like the following

an:(-1)^n*2^n/n!
N:8
S1:ev(makelist(k,k,1,N),simp)
S2:maplist(lambda([ex],ev(an,n=ex)),S1)
S3:ev(S2,simp)
S4apply("+",S3)

Of course, to print out one line in the worked solution you can also apply("+",S2) as well.

To create the binomial coefficients

simp:false;
n:5;
apply("+",map(lambda([ex],binomial(n,ex)*x^ex), ev(makelist(k,k,0,5),simp)));

Surds

Imagine you would like the student to expand out . There are two tests you probably want to apply to the student's answer.

  1. Algebraic equivalence with the correct answer: use ATAlgEquiv.
  2. That the expression is "expanded": use ATExpanded.

You probably then want to make sure a student has "gathered" like terms. In particular you'd like to make sure a student has either but not This causes a problem because ATComAss thinks that So you can't use ATComAss here, and guarantee that all random variants will work by testing that we really have for example.

What we really want is for the functions sqrt and + to appear precisely once in the student's answer, or that the answer is a sum of two things.

Control of surds

See also the Maxima documentation on radexpand. For example

radexpand:false$
sqrt((2*x+10)/10);
radexpand:true$
sqrt((2*x+10)/10);

The first of these does not pull out a numerical denominator. The second does.

Trig simplification

Maxima does have the ability to make assumptions, e.g. to assume that is an integer and then simplify to . Assume the student's answer is ans1 then define the following feedback variables:

declare(n,integer);
sans1:ev(trigrat(ans1),simp);

The variable sans1 can then be used in the PRT. Just note that trigrat writes powers of trig functions in terms of multiple angles. This can have an effect of "expanding" out an expression. E.g. trigrat(cos(n)^20) is probably still fine, but trigrat(cos(n)^2000) is probably not! For this reason trigrat is not part of the default routines to establish equivalence. Trig simplification, especially when we make assumptions on variables like , needs to be done on a question by question basis.

Boolean functions

See the page on propositional logic.

Further examples

Some further examples are given elsewhere:

Note also that question tests do not simplify test inputs.