Programming in Maxima has two very different aspcets:
~/Maxima-5.9.1/share/maxima/5.9.1/share
~/Maxima-5.9.1/share/maxima/5.9.1/src
The use of the Maxima programming language is highly recommended for serious work. Programming with Lisp is work for advanced users. Occassionally, it may be necessary to use both programming languages to solve a problem. The use of files is a candidate for bilingual programming.
A simple Maxima program is nothing but a sequence of Maxima commands. To be able to reproduce a lengthy computation at a later time, it is often prudent to record its commands in a batch file. Such a protocol file is in fact a Maxima program.
In the chapter about Tubes around Spacial Curves we used this definition to compute the tangent of a spacial curve:
tangent(fn, x) := diff(fn(x), x) / ((diff(fn(x), x) . diff(fn (x), x))^(1/2))$
This is a straightforward definition, but it has a serious flaw: It computes the derivative of f three times. It would be much better to compute the derivative only once and to store it for later use.
tangent(fn, x) := block ( [df: diff(fn(x), x)], df / (df . df)^(1/2) )$
Explanations:
[df: diff(fn(x), x)]
df / (df . df)^(1/2)
An expression is either an atom or a structure that is built with an operator and a list of arguments. The function op is used to access the operator of an expression, the function args is used to obtain a list of all arguments. Some examples explain this:
op (a + b + c); + args(a + b + c); [c, b, a] op (sin(3*x)); sin args (sin(3*x)); [3 x] op (f(a, b, g(c), d)); f args (f(a, b, g(c), d)); [a, b, g(c), d]
Note that exp(x) is translated into a power to the constant %e:
op(exp(2*x)); ^ args(exp(2*x)); [%e, 2 x]
A non-atomar expression contains always exactly one operator and a collection of arguments that are in turn expressions. An expression is thus a tree. It is sometimes helpful to traverse that tree and to collect all operators. The knowledge of all used operators can be used to select promising simplifications and to skip simplifications that are obviously not needed in the absence of some operators.
This section explains how such a function is programmed.
We want a function that takes an expression and answers a list with all operators of the expression:
allOps(a); [] allOps(sin(x) + cos(x)); [cos, sin, +] allOps(sin(x) + 2*sin(2*x)); [sin, *, +]
The answered list should contain every found operator once. Repetions are not desired. The last expression exemplifies this requirement.
The function allOps is very simple: It delegates the traversal of the expression to a function that takes a subexpression and a list of operators that were found so far:
allOps(expression):= block( [ ], allOpsPriv (expression, []) );
The function allOpsPriv requires more work. A first attempt handles only the simple cases:
/* incomplete preliminary version */ allOpsPriv(expression, opList) := block ( [x], if atom(expression) then opList else (x: op(expression), cons(x, opList) ) );
This definition handles two simple cases:
The definition uses an if-statement to identify and to handle both cases. An if-statement always answers the value of its evaluated alternative as its result. For an atomar expression, the above if-statement answers the value of opList. The else-branch of the if-statement contains two statements that are written in parentheses. The result of cons(x, opList) is the result of the entire if-statement when the else-branch is selected for evaluation.
Note that the definition of allOpsPriv uses a temporary variable to store the operator of a nonatomar expression. This is not really necessary, it is also possible to write:
cons(op(expression), opList)
When we try the definition that we have now, we obtain:
allOps(a); [] allOps(sin(x) + cos(x)); [+]
The inspection of the subexpressions is obviously missing. For a complete solution we have to:
A refinement of this definition requires additional actions in the else-branch of the if-statement.
Added code is displayed in bold:
allOpsPriv(expression, opList) := block ( [x, args, newList], if atom(expression) then opList else (x: op(expression), args: args(expression), newList: cons(x, opList), for arg in args do newList: allOpsPriv(arg, newList), newList ) );
Here we use the language element
for arg in args do <statement>
A quick test reveals both remarkable improvements and an imperfection of the algorithm:
allOps(a); [] allOps(sin(x) + cos(x)); [cos, sin, +] allOps(sin(x) + 2*sin(2*x)); [sin, *, sin, *, +]
The operators sin and * are mentioned twice. This is not really a surprise, since we do not check whether an operator is already in the list before we add it. It is however easy to fix that:
allOpsPriv(expression, opList) := block ( [x, args, newList], if atom(expression) then opList else (x: op(expression), args: args(expression), newList: if member (x, opList) then opList else cons(x, opList), for arg in args do newList: allOpsPriv(arg, newList), newList ) );
You find this code in file allOps.mc. When you copy that file into the subdirectory user of your Maxima installation, you can load it with
batch("allOps");
The function allOps collects all operators of an expression. To do that, it has to completely traverse an expression. For a similar problem complete traversal is not allways needed:
When we are looking for the presence of a given operator in an expression, we can stop traversal as soon as we have found the operator we are looking for.
containsOp(expression, opList) := block ( [x, args, found], if atom(expression) then false else (x: op(expression), args: args(expression), if member (x, opList) then true else (found : false, for arg in args while not found do found: containsOp(arg, opList), found) ) );
Here we use a variant of the earlier used language element for arg in args do:
for arg in args while not found do <statement>