|
|
Programação no Maxima tem dois diferentes aspectos:
~/Maxima-5.9.2/share/maxima/5.9.2/share
~/Maxima-5.9.2/share/maxima/5.9.2/src
O uso da liguagem de programação do Maxima é fortemente recomendado para trabalhos sérios. Programar em Lisp é trabalho para usuários avançados. Ocasionalmente, pode ser necessário usar ambas as linguagens de programaçã para resolver um problema. O uso de arquivos é um candidato a programação nas duas linguagens.
Um programa simples do Maxima nada mais é que uma seqüência de comando do Maxima. Para estar apto a reproduzir uma computação longa mais tarde, é muitas vezes prudente gravar esses comandos em um arquivo de lote. Tal arquivo de protocolo é de fato um programa do Maxima.
No capítulo sobre Tubos abraçando Curvas Espaciais usamos essa definição para calcular a tangente de uma curva espacial:
tangent(fn, x) :=
diff(fn(x), x) / ((diff(fn(x), x) . diff(fn (x), x))^(1/2))$
Isso é uma definição direta, mas tem uma falha séria: calcula a derivada de f três vezes. Pode ser muito melhor calcular a derivada somente uma vez e guardá-la para usaromais tarde.
tangent(fn, x) :=
block ( [df: diff(fn(x), x)],
df / (df . df)^(1/2)
)$
Explanações:
[df: diff(fn(x), x)]
df / (df . df)^(1/2)
Uma expressão é ou um átomo ou uma estrutura que é construída com um operador e uma lista de argumentos. A função op é usada para acessar o operador de uma expressão, a função args é usada para obter uma lista de todos os argumentos. Alguns exemplos mostram isso:
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 que exp(x) é traduzida em um expoente para a constante %e:
op(exp(2*x));
^ args(exp(2*x)); [%e, 2 x]
A expressão não atômica contém sempre exatamente um operador e uma coleção de argumentos que estão em uma expressão anterior. uma expressão é dessa forma uma árvore. Isso é algumas vezes útil para percorrer aquela árvore e coletar todos os operadores. O conhecimento de todos os operadores usados pode ser usado para selecionar simplificações promissoras e omitir simplificações que obviamente são desnecessárias no contexto de alguns operadores.
Essa seção faz uma explanação como tal função é programada.
Queremos uma função que receba uma expressão e retorne uma lista com todos os operadores da expressão:
allOps(a);
[] allOps(sin(x) + cos(x)); [cos, sin, +] allOps(sin(x) + 2*sin(2*x)); [sin, *, +]
A lista respondida pode conter todo operador encontrado pelo menos uma vez. Repetições não são sesejadas. A última expressão exemplifica esse requerimento.
A função allOps é muito simples: delega o exame da expressão a uma função que recebe uma subexpressão e uma lista de operadores que forem encontrados então:
allOps(expression):=
block( [ ],
allOpsPriv (expression, [])
);
A função allOpsPriv requer mais trabalho. Uma primeira tentativa de manusear somente casos simples:
/* incomplete preliminary version */ allOpsPriv(expression, opList) := block ( [x], if atom(expression) then opList else (x: op(expression), cons(x, opList) ) );
Essa definição maneja dois casos simples:
A definição usa uma declaração if para identificar e manusear ambos os casos. Uma declaração if sempre responde o valor de sua alternativa avaliada como seu resultado. Para uma expressão atômica, a declaração if acima responde o valor de opList. o ramo else da declaração if contém duas declarações que são escritas entre parêntesis. O resultado de cons(x, opList) é o resultado completo da declaração if quando o ramo else é selecionado para avaliação.
Note que a definição de allOpsPriv usa uma variável temporária para armazenar o operador de uma expressão não atômica. Isso não é realmente necessário, isso pode ser escrito:
cons(op(expression), opList)
Quando tentamos a definição que temos agora, obtemos:
allOps(a);
[] allOps(sin(x) + cos(x)); [+]
O exame de uma subexpressão é obviamente omitida. Para uma solução completa temos que:
Um refinamento dessa definição requer ações adicionais no ramo else da declaração if.
O código adicionado é mostrado em negrito:
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 ) );
Aqui usamos o elemento da linguagem
for arg in args do
<statement>
Um rápido teste revela ambas as consideráveis melhorias e uma imperfeição do algorítmo:
allOps(a);
[] allOps(sin(x) + cos(x)); [cos, sin, +] allOps(sin(x) + 2*sin(2*x)); [sin, *, sin, *, +]
Os operadores sin e * são emncionados duas vezes. Isso não é realmente uma surpresa, uma vez que nós não verificamos se um operador já se encontra na lista antes de adicioná-lo. Isso é todavia facilmente corrigido aqui:
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
)
);
Você encontra esse código no arquivo allOps.mc. Quando você copiar aquele arquivo no subdiretório user de sua instalação do Maxima, você pode chamá-lo com
batch("allOps");
A função allOps coleta todos os operaadores de uma expressão. Para fazer isso, ela tem que percorrer completamente uma expressão. Para um problema similar o exame completo da expressão não é sempre necessário:
Quando estamos olhando pela presença de um dado operador em uma expressão, podemos parar o eexame da expressão tão logo encontremos o operador que estamos procurando.
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)
)
);
Aqui usamos uma variante do elemento de linguagem usado anteriormente for arg in args do:
for arg in args while not found do
<statement>
|
|