• Keine Ergebnisse gefunden

3 Multi-terminal decision diagrams with addition zur komprimierten Darstellung von Matrizen

4.3 Matrix-Operationen

4.3.2 Matrix transponieren

4.3.6.1 Das Modul CoefficientMatrix

Da im Rahmen der Funktion matrixEquality mit Koeffizientenmatrizen gearbeitet wird und es sich dabei in gewisser Weise um eine eigenständige Thematik handelt, erschien es in unseren Augen sinnvoll, ein eigenes Modul namens CoefficientMatrix zu erstellen, welches Funktionen zur Verfügung stellt, die auf Koeffizientenmatrizen operieren.

Neben der Hauptfunktion calcEssentialEqs und ihrer größeren Hilfsfunktion gaussJordanIteration, auf welche wir gleich eingehen werden, enthält das Modul eine Datentypdefinition sowie eine Reihe kleinerer Funktionen:

Wir definieren eine Matrix als Liste von Listen, wobei diese Listen Werte beliebigen Typs enthalten dürfen. Jede innere Liste repräsentiert eine Zeile der Matrix.

Die Funktion cntEmptyCols zählt bei Eingabe einer Matrix von links die Anzahl der Spalten, welche ausschließlich Nullen enthalten.

Mittels swap lassen sich innerhalb einer Matrix zwei Zeilen vertauschen.

scale multipliziert jeden Wert innerhalb einer Zeile mit einem bestimmten Wert.

Um ein Vielfaches einer Zeile auf eine andere Zeile aufzuaddieren, existiert die Funktion addRow.

Die Hauptfunktion des Moduls namens calcEssentialEqs bekommt als Eingabe eine transponierte Koeffizientenmatrix, wobei jede Spalte die Koeffizienten einer Gleichung beinhaltet, und berechnet eine Liste von Spaltenindizes einer minimalen Menge von Gleichungen, welche die gleiche Lösungsmenge hat wie das ursprüngliche Gleichungssystem (wir erinnern an dieser Stelle nochmal an die in Kapitel 2.2.3 festgestellte Analogie zwischen Familien von Vektoren und linearen Gleichungssystemen). Um diese

Menge zu berechnen, benutzen wir eine abgewandelte Variante des Gauß-Jordan-Algorithmus:

Die Matrix wird unter Anwendung von Schritt 1 des Gauß-Jordan-Algorithmus sukzessive verkleinert. Hierbei modifizieren wir den Schritt 1.1 allerdings erheblich: Anstatt die erste Spalte zu suchen, die nicht ausschließlich aus Nullen besteht, entfernen wir auf der linken Seite der aktuellen Matrix alle Spalten, die ausschließlich Nullen beinhalten – die mit diesen Spalten korrespondierenden Gleichungen lassen sich nämlich auf jeden Fall als Linearkombination anderer Gleichungen darstellen, da diese Spalten die im Grundlagenteil beschriebenen Anforderung an eine Spalte, welche eine essentielle Gleichung repräsentiert (enthält in der reduzierten Zeilenstufenform eine führende Eins), nicht mehr erfüllen können.

Dies verdeutlichen wir anhand einer Grafik:

(Ein x steht für einen beliebigen Zahlenwert.)

Die rot eingefärbten Zeilen und Spalten wurden bereits im Lauf des Algorithmus entfernt, die aktuelle Matrix ist grau eingefärbt. Es ist offensichtlich, dass keiner der Schritte des Gauß-Jordan-Algorithmus etwas daran ändern kann, dass die erste Spalte der aktuellen Matrix nur aus Nullen besteht. Auch im bereits entfernten Teil der Spalte kann offensichtlich im Verlauf des Algorithmus keine führende Eins mehr auftreten – alle bereits entfernten Zeilen enthalten rechts der fraglichen Spalte führende Einsen. Somit ist die zu dieser Spalte gehörige Gleichung redundant, denn sie lässt sich als Linearkombination anderer Gleichungen darstellen.

Gleichermaßen kann man sich überlegen, dass (mit einer Ausnahme) alle Spalten, die nicht im Laufe der Berechnung aus oben genannten Gründen entfernt werden, mit essentiellen Gleichungen korrespondieren müssen. Auch dies zeigen wir anhand einer Grafik:

Die erste Spalte der aktuellen (grau eingefärbten) Matrix ist bereits normalisiert, d.h. Schritte 1.2-1.4 des Gauß-Jordan-Algorithmus wurden bereits vorgenommen, weswegen die Spalte aus einer Eins in der ersten Zeile und ansonsten aus Nullen besteht. Wie wir sehen, handelt es sich bei dieser Eins definitiv um eine führende Eins. Weiterhin wissen wir, dass alle in der Spalte über dieser führenden Eins vorkommenden aktuell noch beliebigen Einträge durch Schritt 2 des Gauß-Jordan-Algorithmus in Null umgewandelt werden können. Somit enthält diese Spalte in der reduzierten Zeilenstufenform definitiv eine führende Eins und erfüllt somit die Anforderung, die an eine Spalte gestellt wird, welche eine essentielle Gleichung enthält.

Die Ausnahme: Falls die Berechnung irgendwann dazu führt, dass die aktuelle Matrix nur noch aus einer einzigen Zeile besteht, korrespondiert von den übrigen Spalten nur noch maximal eine mit einer essentiellen Gleichung, und zwar diejenige, welche als erstes einen Wert enthält, der nicht Null ist.

Durch den Umstand, dass wir bereits bei der Durchführung des oben dargestellten Verfahrens für jede Spalte sagen können, ob ihre korrespondierende Gleichung essentiell oder redundant ist, können wir uns Schritt 2 des Gauß-Jordan-Algorithmus sparen.

Kommen wir nun zur Implementierung:

Die Koeffizientenmatrix, die der Funktion calcEssentialEqs übergeben wird, ist bzgl. ihrer Elemente auf reelle Zahlen beschränkt, um gewährleisten zu können, dass es bei den Zeilenmanipulationen nicht zu Rundungsfehlern kommen kann.

calcEssentialEqs wandelt alle Elemente der Koeffizientenmatrix in Werte des Typs Rational um und ruft dann die Hilfsfunktion calcEssentialEqs' auf, welche neben der Matrix selbst über zwei weitere Parameter verfügt: Den Index der aktuell ersten Spalte sowie eine Liste mit den Indizes derjenigen Spalten, welche mit essentiellen Gleichungen korrespondieren – diese ist natürlich zu Anfang zunächst leer.

calcEssentialEqs' verkleinert die Matrix wie oben beschrieben sukzessive durch rekursive Aufrufe, wobei bei jedem Aufruf zunächst überprüft wird, ob sich auf der linken Seite der Matrix Spalten befinden, die nur aus Nullen bestehen – werden solche gefunden, werden sie entfernt und die Funktion erneut mit aktualisiertem Zeiger auf die neue erste Spalte aufgerufen. Ansonsten wird die Matrix mittels der Hilfsfunktion partialGaussJordan gemäß der Schritte 1.2-1.5 des Gauß-Jordan-Algorithmus zunächst modifiziert und anschließend reduziert. Dem folgt ein neuer rekursiver Aufruf, bei dem der aktuelle Index in die Liste der essentiellen Gleichungen aufgenommen wurde.

calcEssentialEqs' wird so lange rekursiv aufgerufen, bis die Matrix nur noch aus einer einzelnen Zeile (gleichbedeutend mit einem einzelnen Wert) oder einer einzelnen Spalte besteht. Im Fall einer einzelnen Zeile werden zunächst führende Nullen entfernt, woraufhin ein weiterer rekursiver Aufruf erfolgt. Falls die Matrix daraufhin leer ist, werden die im Berechnungsverlauf gesammelten Indizes der essentiellen Gleichungen ausgegeben. Hat sie allerdings noch Werte, wird der Index der aktuelle Spalte zur Liste der Indizes hinzugefügt und diese ausgegeben. Im Fall einer einzelnen Spalte wird lediglich überprüft, ob diese von Null verschiedene Werte enthält. Ist dies der Fall, wird die Liste der Indizes inklusive der aktuellen Spalte, ansonsten exklusive der aktuellen Spalte ausgegeben.

calcEssentialEqs :: Real a => Matrix a -> [Int]

calcEssentialEqs m = calcEssentialEqs' (map (map toRational) m) 0 []

where

calcEssentialEqs' ([] : xs) curr eqs = eqs calcEssentialEqs' m@[x] curr eqs =

let numEmptyCols = cntEmptyCols m in if numEmptyCols == 0

then (curr : eqs)

else calcEssentialEqs' (map (drop numEmptyCols) m) (curr + numEmptyCols) eqs calcEssentialEqs' m@([x] : xs) curr eqs =

if cntEmptyCols m == 0

then calcEssentialEqs' (gaussJordanIteration m) (curr + 1) (curr:eqs) else calcEssentialEqs' (map (drop numEmptyCols) m)

(curr + numEmptyCols) eqs

Die Funktion partialGaussJordan führt wie bereits erwähnt die Schritte 1.2-1.5 des Gauß-Jordan-Algorithmus durch. Zunächst wird die erste Zeile ermittelt, welche an vorderster Position ein von Null verschiedenes Element besitzt (der Wert dieses Elements wird ebenfalls gespeichert). Anschließend wird die ermittelte Zeile mit der ersten der Matrix vertauscht. Daraufhin werden alle Elemente innerhalb der ersten Zeile mittels scale durch das erste Zeilenelement geteilt, sodass am Anfang der Zeile eine Eins steht. Im nächsten Schritt sorgt die Hilfsfunktion normalizeFstCol dafür, dass durch subtrahieren entsprechender Vielfacher der ersten Zeile die erste Spalte abgesehen vom obersten Element nur Nullen enthält. Letztendlich wird mittels removeFstRowAndCol die erste Zeile und erste Spalte entfernt.

gaussJordanIteration :: Matrix Rational -> Matrix Rational gaussJordanIteration m =

let

fstRowWithNonZeroElem = findFstRowWithNonZeroElem m fstNonZeroElem = head (m !! fstRowWithNonZeroElem) in

removeFstRowCol . normalizeFstCol (length m - 1) .

scale 0 (1 / fstNonZeroElem) . swap 0 (fstRowWithNonZeroElem) $ m where

findFstRowWithNonZeroElem [] = error "All rows begin with a zero"

findFstRowWithNonZeroElem (r : rs) =

if (head r) == 0 then 1 + (findFstRowWithNonZeroElem rs) else 0 normalizeFstCol 0 m = m

normalizeFstCol k m =

let headOfCurrRow = head (m !! k) in if headOfCurrRow == 0

then normalizeFstCol (k-1) m

else normalizeFstCol (k-1) (addRow 0 (negate headOfCurrRow) k m) removeFstRowCol [x] = []

removeFstRowCol m = map (drop 1) . tail $ m