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.
- Only one
orderless
orordergreat
command can be issued in any session. The last one encountered will be used and the others ignored. - No warnings or errors are issued if more than one is encountered.
- The
orderless
orordergreat
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:
- 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. - Use evaluation flags to control the level of simplification for an individual CAS expression, for example:
{@3/9,simp=false@}
- 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 withsimp: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 predicatezeroMulp
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 predicatezeroMulp(1-1)
is false. This sub-tree is not selected by this predicate.p2
displays as . The displayed expression is subjected to the functionsimplify
, and the displayed part replaced. The rest of the expression is unchanged. By default thedisp_select
is removed and so the result is not coloured and underlined.p3
displays as . Notice the third, optional boolean, argument toselect_apply
inp3
. This argument will decide whether to continue to display thedisp_select
display or remove it (now the function has been applied). The default istrue
, 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.
- Algebraic equivalence with the correct answer: use
ATAlgEquiv
. - 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:
- Matrix examples in showing working.
- An example of a question with
simp:false
is discussed in authoring quick start 7. - Generating random algebraic expressions which need to be "gathered and sorted".
Note also that question tests do not simplify test inputs.