weitergehende Möglichkeiten
Kommandozeilen-Optionen
Yacas arbeitet standardmäßig im interaktiven Modus. Die Arbeitsweise von Yacas kann
über verschiedene Optionen gesteuert werden, die auch kombiniert werden können.
Mit dieser Option wird die Ausgabe der Prompts "In>" und "Out>" unterdrückt. Dies
ist sinnvoll, wenn im nicht interaktiven Modus gearbeitet wird.
Durch Setzen dieser Option wird die Verwendung besonderer Terminaleigenschaften
wie z.B. Editierfunktionen oder Escape-Sequenzen unterdrückt. Dies ist sinnvoll,
wenn im nicht interaktiven Modus gearbeitet wird.
Mit dieser Option wird das Verhalten beim Aufruf von Befehlen aus der "history"-Liste
verändert. Nach der Ausführung einer solchen Anweisung erscheint sofort die in der
"history"-Liste folgende Anweisung in der Eingabezeile.
Die Datei "filename" wird eingelesen und die enthaltenen Yacas-Anweisungen werden
ausgeführt. Die Funktion entspricht der der Funktion "Load()".
Gibt die Yacas Versionsnummer aus.
Gibt den Pfad zum Yacas-Verzeichnis aus.
Anweisungsblöcke
Mehrere Anweisungen können mit Hilfe von eckigen Klammern zusammengefasst werden.
Der Anweisungsblock [a;b;c;]; berechnet erst a, dann b und schließlich
c. Das Ergebnis von c wird zurückgegeben.
Innerhalb eines Anweisungsblocks können mit der Funktion Local(var1, var2,...)
lokale Variablen definiert werden.
"Threading" von Funktionen
Bei einer Reihe von Yacas-Funktionen darf das Argument nicht nur ein einfacher
Ausdruck sein, sondern kann auch eine Liste sein. Die Funktion wird dann auf jedes
Listenelement angewendet, die Ergbnisse werden als Liste zurückgegeben. Solche
Funktionen heißen threaded.
In> Sin({a,b,c});
Out> {Sin(a),Sin(b),Sin(c)};
|
Fast alle analytischen Funktionen sowie die arithmetischen Operatoren haben diese
Eigenschaft.
Funktionen als Listen
In Yacas werden Zahlen und Variablen intern als Strings dargestellt. Zusammengesetzte
Ausdrücke werden wie in LISP als Listen dargestellt. Jeder Ausdruck kann mit der
Funktion Listify() in eine Liste umgewandelt werden. Umgekehrt kann
eine Liste mit UnList() in einen Term umgewandelt werden.
In> Listify(a+b*(c+d));
Out> {+,a,b*(c+d)};
In> UnList({Atom("+"),x,1});
Out> x+1;
|
Das erste Listenelement ist der Name der Funktion +. Der Teilterm
"b*(c+d)" wurde nicht in die Listenform gebracht.
Grundlagen der Yacas-Syntax
Die Yacas-Syntax entspricht im wesentlichen der üblichen mathematischen Schreibweise.
Für die Auswertung von Termen gelten die üblichen Rechenregeln. Mit Hilfe von runden
Klammern kann die Rechenreihenfolge explizit vorgegeben werden, z.B.
a*(b+c). Die Syntax ähnelt der der Programmiersprache C. Yacas
unterscheidet zwischen Klein- und Großschreibung.
Es gibt zwei Formen von Funktionen, f(x,y) und f(x) y.
Bei der zweiten Form steht das letzte Argument außerhalb der Klammer mit den übrigen
Argumenten. Diese Funktionen heißen in Yacas "bodied". In dieser Schreibweise kann
die Funktion "f(x)" als Operator angesehen werden, der auf "y" wirkt. Typische
Beispiele sind die Funktionen While (predicate) body; oder der
Ableitungsoperator D(x) expr.
Intern werden alle Funktionen und Operatoren gleich dargestellt. Operatoren und
Funktionen können beliebig definiert und umdefiniert werden. Jedem Operator kann
ein Rang zugeordnet werden, z.B. hat der Multiplikationsoperator *
einen höheren Rang als der Additionsoperator +.
Die Möglichkeiten soll anhand einiger Beispiele verdeutlicht werden.
Im ersten Beispiel soll die Funktion F(x,y)=x/y+y/x definiert werden. In der
Standardsyntax lautet die Definition:
In> F(a,b) := a/b + b/a;
Out> True;
|
Die Funktion kann jedoch auch als Operator definiert werden. Im folgenden wird ein
neuer Operator mit dem Namen xx definiert:
In> Infix("xx", OpPrecedence("/"));
Out> True;
In> a xx b := a/b + b/a;
Out> True;
In> 3 xx 2 + 1;
Out> 19/6;
|
Der Rang des Operators wird in der ersten Zeile mit der Funktion
OpPrecedence() gesetzt. Er entspricht hier dem des
Divisionsoperators.
Schließlich soll noch die Möglichkeit gezeigt werden, diese Funktion als "bodied"
zu definieren:
In> Bodied("##", OpPrecedence("/"));
Out> True;
In> ##(a) b := a xx b;
Out>
True;
In> ##(1) 3 + 2;
Out> 16/3;
|
Der Name "##" ist willkürlich gewählt. In der aktuellen Yacas Version gibt es nur
eine Einschränkung bei der Wahl eines Funktionsnamens: Ist die Funktion einmal als
"infix", "prefix", "postfix" oder "bodied" definiert, dann wird sie im folgenden
immer entsprechend interpretiert. Wurde z.B. die Funktion "f" als "bodied" definiert,
dann können im folgenden weitere Funktionen mit dem gleichen Namen "f" definiert
werden, die sich in der Anzahl der Argumente unterscheiden. Alle diese Funktionen
haben aber die Eigenschaft "bodied".
definieren von Vereinfachungsregeln
Für das symbolische Rechnen sind vielseitige Vereinfachungsregeln erforderlich.
Anstatt alle möglichen Umformungsregeln in einer Bibliothek zu sammeln, stellt Yacas
einen auf Mustererkennung basierenden Mechanismus zur Verfügung, mit dessen Hilfe
der Anwender leicht eigene Vereinfachungsregeln aufstellen kann. Yacas enthält eine
große Bibliothek mit Regeln, die Muster erkennen und ersetzen können. Beispiele
finden sich in den Dateien "standard", "stdfuncs", "deriv" und "solve", die in der
Yacas-Distribution enthalten sind.
Eine einfache Anwendung von Mustererkennungsregeln ist die Definition neuer
Funktionen. Als Beispiel soll eine Funktion "f" zur Berechnung der Fakultät
natürlicher Zahlen definiert werden. Zunächst wird eine boolsche Funktion definiert,
die überprüft, ob das Argument eine natürliche Zahl ist. Die Fakultät selbst wird
mit der Gleichung f(n)=n*f(n-1) rekursiv berechnet:
IsIntegerGreaterThanZero(_n) <-- IsInteger(n) And n>0;
10 # f(0) <-- 1;
20 # f(n_IsIntegerGreaterThanZero) <-- n*f(n-1);
|
In der ersten Zeile wird die boolsche Funktion IsIntegerGreaterThanZero()
definiert. Dies Definition dieser Funktion ist eigentlich überflüssig und soll nur
die Vorgehensweise verdeutlichen. Yacas enthält für diesen Zweck bereits die Funktion
IsPositiveInteger. In den zwei folgenden Zeilen werden die Regeln für
die Rekursion definiert. Die Regeln erhalten den Rang 10 bzw. 20. Die erste Regel
wird also zuerst angewendet.
Mit dem Operator <-- wird eine Regel definiert. Der Parameter
_n in der Regel für IsIntegerGreaterThanZero() spezifiziert, dass
das Argument der lokalen Variablen n zugewiesen wird. Auf der rechten
Seite des Operators <-- wird die Variable n (ohne
Unterstrich) verwendet.
Nun zu den beiden Regeln für die Funktion f. Die erste Regel legt fest, dass
f(0) durch 1 ersetzt wird. In der zweiten Regel steht n_IsIntegerGreaterThanZero
für das Argument von f unter der Voraussetzung, dass das Ergebnis von
IsIntegerGreaterThanZero(n) True ist. Der
Unterstrich-Operator darf nur auf der linken Seite des <-- Operators
verwendet werden.
Die zweite Regel kann auch folgendermaßen definiert werden:
20 # f(_n)_(IsIntegerGreaterThanZero(n)) <-- n*f(n-1);
|
Der Unterstrich hinter der Funktion ist eine boolsche Funktion, die das Ergebnis
True liefern muss, ansonsten wird die Regel nicht angewendet. Die
boolsche Funktion kann auch eine komplizierte Verknüpfung verschiedener logischer
Operationen sein.
Werte für den Rang werden als Zahl mit folgendem # Operator angegeben.
Die Zahl bestimmt die Rangfolge der Regeln. 0 ist der kleinste erlaubte Rang. Regeln
mit kleinem Rang werden zuerst angewendet. Haben Regeln den gleichen Rang, dann
bedeutet dies, dass die Reihenfolge unerheblich ist. Wird kein Rang angegeben, dann
wird 0 angenommen. In dem Beispiel muss die Regel f(0)=1 zuerst angewendet
werden, da sonst eine unendliche Rekursion die Folge wäre. Die Zahlen 10 und 20 sind
beliebig, es kommt nur auf die Ordnung an.
Ergänzende boolsche Funktionen sind ebenfalls möglich. IsIntegerGreaterThanZero
kann auch wie folgt definiert werden:
10 # IsIntegerGreaterThanZero(n_IsInteger)_(n>0) <-- True;
20 # IsIntegerGreaterThanZero(_n) <-- False;
|
Die erste Regel legt fest, dass das Ergebnis True ist, falls "n" eine
ganze Zahl größer als Null ist. Die zweite Regel legt fest, dass das Ergebnis in
allen anderen Fällen False ist.
Die linke Seite einer Regel hat immer die Form
"pattern _ postpredicate <-- replacement".
Der Zusatz (n>0) folgt dem Muster, das nur ausgewertet wird, wenn
das Ergebnis von (n>0) True ist.
Die Regeln F(n_IsPositiveInteger)<--... und
F(_n)_(IsPositiveInteger(n)) <-- ... sind äquivalent.
Weiter Beispiele für Regeln:
_x + 0 <-- x;
_x - _x <-- 0;
ArcSin(Sin(_x)) <-- x;
|
Namen mit einem Unterstrich am Anfang oder am Ende sind Platzhalter für beliebige
Objekte, z.B. Zahlen, Listen usw. Jedem solchen Namen ist innerhalb der Regel eine
lokale Variable zugeordnet. In der Vereinfachungsregel _x - _x <-- 0;
wird festgelegt, dass die Objekte links und rechts vom Minuszeichen gleich sind.
Vereinfachungsregeln können auch mit Hilfe der Funktionen RuleBase()
und Rule() definiert werden. Die beschriebene Vorgehensweise über
"... # ... <-- ..." ist jedoch leichter lesbar und hinreichend
flexibel.
lokale Vereinfachungsregeln
Mitunter sollen spezielle Vereinfachungsregeln, die nicht standardmäßig angewendet
werden, nur auf einen Ausdruck angewendet werden. Solche lokalen Regeln werden mit
Hilfe der Operatoren /: und /:: realisiert.
Angenommen ein Ausdruck enthält die Funktion Ln(a*b), die in
Ln(a)+Ln(b) umgewandelt werden soll. Die Regel wird mit dem
/: Operator definiert:
In> Sin(x)*Ln(a*b)
Out> Sin(x)*Ln(a*b);
In> % /: { Ln(_x*_y) <- Ln(x)+Ln(y) }
Out> Sin(x)*(Ln(a)+Ln(b));
|
Die Liste kann beliebig viele Regeln enthalten, die alle auf den Ausdruck auf der
linken Seite angewendet werden.
Die Regel kann dabei eine der folgenden Formen haben:
pattern <- replacement
{pattern,replacement}
{pattern,postpredicate,replacement}
Anstelle des in globalen Regeln üblichen <-- Operators wird in
lokalen Regeln der <- Operator verwendet.
Der /: Operator durchläuft einen Ausdruck von oben nach unten und
versucht die in der Liste enthaltenen Regeln der Reihe nach anzuwenden. Können die
Regeln nicht angewendet werden, werden die Teilausdrücke untersucht.
Manchmal ist es notwendig, eine Regel mehrfach anzuwenden. Der /::
Operator wendet den /: Operator so oft an, bis sich das Ergebnis
nicht mehr ändert. Der Operator sollte mit Vorsicht eingesetzt werden, da leicht
Totschleifen entstehen können. Solche Situationen können erkannt werden, wenn
zunächst der /: Operator wiederholt angewendet wird.