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.

yacas -c

Mit dieser Option wird die Ausgabe der Prompts "In>" und "Out>" unterdrückt. Dies ist sinnvoll, wenn im nicht interaktiven Modus gearbeitet wird.

yacas -p
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.

yacas -t

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.

yacas {filename}

Die Datei "filename" wird eingelesen und die enthaltenen Yacas-Anweisungen werden ausgeführt. Die Funktion entspricht der der Funktion "Load()".

yacas -v

Gibt die Yacas Versionsnummer aus.

yacas -d

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.