Geschrieben von: Christoph Rüegg
Math.NET ist in erster Linie eine Bibliothek (Komponente) die von Softwareentwicklern in ihre Applikationen eingebunden werden kann. Die Anwender kommunizieren mit der Bibliothek jedoch zumeist primär über Text, den sie via Tastatur eingeben oder den sie von der Anwendung als Ausgabe zurückbekommen. Wie üblich in der Computerwelt muss dieser Text einem Haufen von Regeln genügen, damit sich Computer und Anwender gegenseitig verstehen.
Die Math.NET Parser können mit verschiedenen Notationen umgehen. Die wichtigste dabei ist mit Abstand die sog. Infix Notation, die weitgehend der im Mathematik Unterricht gelehrten Schiebweise entspricht. Um die Eindeutigkeit zu wahren sowie die Beschränkungen der Tastatur zu umgehen wurde eine spezielle Syntax und Semantik entwickelt, die in diesem Dokument eingeführt wird. Auf alternative Notationen wie Post- und Prefix wird hier nicht weiter eingegangen.
Die Grundrechenarten funktionieren wie gewohnt:
2+31 4*x -2/5 3.1415 2^3
Die einzelnen Ausdrücke werden durch das Zeilenende (Enter-Taste) abgeschlossen und ausgeführt, als Dezimalpunkt wird der Punkt verwendet, und die Multiplitation muss immer explizit mit dem * notiert werden.
In Math.NET lässt sich praktisch jedes Zeichen als Operatorzeichen überschreiben (auch Buchstaben, wie beispielsweise x für das Kreuzprodukt zweier Vektoren), doch häufig, insbesondere wenn mehrere Parameter benötigt werden, ist der Einsatz von Funktionen vorteilhaft.
sin(pi()) jet(exp(x),x,0,1,5)
Funktionen bestehen aus einem Namen, gefolgt von einer öffnenden und einer schliessenden Klammer. Zwischen den Klammern können beliebig viele Parameter kommagetrennt angegeben werden.
Math.NET ist ein symbolisches Framework, somit haben Variablen eine spezielle Bedeutung. Variablen beginnen immer mit einem Buchstaben, danach sind auch Ziffern möglich. Sie werden beim ersten Vorkommen automatisch im aktuellen Kontext angelegt. Die Zuweisung eines Wertes an eine Variable erfolgt mit dem Doppelpunkt Operator (und nicht etwa mit dem Gleichheitsoperator!):
2*x x:15 x:2-x y:x^3 unlock(x) lock(y)
Variablen können gelockt (per default) und entlockt sein. Entlockte Variablen verhalten sich wie ihr Wert und werden gegebenenfalls duch ihn ersetzt, während sich gelockte Variablen niemals auflösen und somit ihre Allgemeinheit behalten. Mittels lock() können Variablen gelockt werden, mit unlock() werden sie entlockt. Somit ist es möglich, beliebig die Auflösungstiefe anzupassen.
Die Math.NET Ausdruckssprache ist grundsätzlich funktional, d.h. die Ausdrücke sind in sich geschossen und nicht von äusseren Faktoren abhängig (in Gegensatz zu klassisch imperativen Sprachen wie C++ oder Basic). So verändern die Operationen den Systemzustand auch in keiner Weise. Doch wie überall gibt es auch hier eine Ausnahme: Variablen. Zwar sind die Variablen ansich funktionale Elemente, nicht jedoch deren Veränderung. Um Variablen manipulieren zu können wurden daher die (imperativen) Prozeduren eingeführt. Die wichtigste solche Prozedur ist der oben kennengelernte Zuweisungsoperator, weitere werden wir später noch einführen.
Der Parser ansich macht nichts anderes als den Ausdruck, der in Textform vorliegt, in einen Ausdrucksbaum zu übersetzen. Solche Ausdrucksbäume, die auch programmatisch zusammengebaut werden können, sind die zentralen Aktoren der Bibliothek.
Was nun genau mit diesen Bäumen geschieht hängt von der Anwendung ab. Die meisten mitgelieferten Demos nutzen den Formatter der Bibliothek um die Terme/Bäume wieder in Textform auszugeben, zusammen mit ihrer Vereinfachung, Expansion, Ableitung und Integral, sowie, falls konstant, seiner Berechnung. Ist der Ausdruck eine Prozedur, so wird diese ausgeführt. Das erklärt auch das Verhalten beispielsweise des Zuweisungsoperators: Die tatsächliche Zuweisung findet erst beim Formatter statt; nimmt eine Anwendung nur Bäume in eine Liste auf, führt die Prozeduren jedoch nicht explizit aus (oder implizit durch den Formatter), so funktionieren auch keine Zuweisungen!
Math.NET ist streng typisiert, d.h. jeder Knoten in einem Ausdrucksbaum hat einen genau definierten Typ. Bisher haben wir zwei solche Typen kennengelernt. Zum einen ist das der Typ Scalar, der reelle Zahlen (genauer: reelle Ausdrücke) darstellt, wie beispielsweise 3.1415 oder 3+4*5. Zum zweiten is das der Typ Procedure, der eine imperative, ausführbare Prozedur repräsentiert. Daneben gibt es weitere Typen wie Complex für komplexe Zahlen, Vector und Matrix für mehrdimensionale Anwendungen, Literal für Textelemente und TimeSpan für Zeitabschnitte, um nur einige davon zu nennen.
Die verschiedenen Typen lassen sich grundsätzlich nicht untereinander umwandeln, ausser es stehen entsprechende Funktionen zur Verfügung. So liefert real(z) den Realteil der komplexen Variable z - ein Skalar.
Die imaginäre Einheit wird mit eine grossen I repräsentiert. Dieser Buchstabe ist reserviert und kann nicht als Variablenname verwendet werden. Damit kann im Grunde wie gewohnt gearbeitet werden; für die komplexe Zahl (3,5) gibt man also 3+I*4 ein. Auch die polare Notation mit der Exponentialfunktion kann verwendet werden: 5*exp(I*3). Viele Operatoren wurden für die Verwendung mit komplexen Zahlen überladen, somit funktionieren auch Ausdrücke wie (I+sin(I*3))^2.
Math.NET unterscheidet nicht zwischen horizontalen und vertikalen Vektoren. Wenn diese Unterscheidung notwendig ist kann alternativ direkt mit Matrizen gearbeitet werden. Beide Typen werden mit Hilfe von eckigen Klammern eingegeben. Dabei kann man sich eine Matrix wie ein horizontaler Vektor aus vertikalen Vektoren vorstellen:
v1:[2,y,2*y,y^2] v2:v1*[a,b,c,d] [1,2,3]x[4,5,6] m1:[[1,2],[3,4]]*[[5,6],[7,8]] v3:[[1,2,3]] v4:[[1],[2],[3]]
Wichtig dabei ist, dass die Matrix v3 vertikal ist, die Matrix v4 hingegen horizontal. Das Skalarprodukt von Vektoren wird mit * berechnet, während x das Vektorprodukt liefert.