Rheinisch Westfälische Technische Hochschule Institut für Geometrie und Praktische Mathematik Numerische Analysis IV — SS 2007 Prof. Dr. Sebastian Noelle — Dipl. Math. Jörg Grande Übungsblatt P1 Abgabe bis Dienstag, 22.5., 11:45, in den Einwurfkasten vor Raum 149, Hauptgebäude Programmieraufgabe 1 (Massenmatrix) Triangulierungen von Gebieten und Verfahren zur Diskretisierung von Operatoren sind Grundbestandteile aller Finite-Elemente-Programme. Ziel der vorliegenden Aufgabe ist es eine gegebene Triangulierung einzulesen, die Massenmatrix der stetigen, stückweise linearen Finiten Elemente aufzustellen und diese Matrix in einem weiterverwendbaren Format zu speichern. Die Triangulierung eines beschränkten, polygonalen Gebietes Ω ⊂ R2 , das auf einer Seite seines Randes liegt, ist in einer Datei folgender Form gegeben: • Kommentare beginnen mit #“ am Anfang einer Zeile und enden am Zeilenende. Sie dürfen an jeder Stelle ” in der Datei stehen. • Das Schlüsselwort Vertices“ steht in einer eigenen Zeile. ” • Es folgen, zeilenweise, die Ecken der Triangulierung als Paare reeller Zahlen (x- und y-Koordinate). Sie sind mit mindestens 10stelliger Genauigkeit gegeben. Eckpunkt vi befindet sich in Zeile i nach Vertices“, ” i = 1, 2, 3, . . . , m. Dabei werden Kommentarzeilen nicht mitgezählt. • Das Schlüsselwort Triangles“ steht in einer eigenen Zeile. ” • Es folgen, zeilenweise, die Dreiecke der Triangulierung als Tripel natürlicher Zahlen (Indizes der Eckpunkte aus der Liste Vertices“). Die n Dreiecke sind ebenfalls durch Ihre Zeilennummer nach Triangles“ implizit ” ” von 1 an numeriert. Wieder werden Kommentarzeilen nicht mitgezählt. • Das Ende der Dreiecksliste ist entweder das Dateiende oder das Schlüsselwort EndTriangles“ in einer ” eigenen Zeile. • Innerhalb der Zeilen sind Leerzeichen und Tabulatoren zu Beginn und am Ende erlaubt; sie trennen auch die einzelnen Zahlen der Paare bzw. Tripel. Sie dürfen annehmen, daß die Triangulierung konsistent ist und mindestens ein Dreieck enthält. Entwerfen Sie eine möglichst einfache Datenstruktur Grid, um diese Listen in Ihrem Programm zu repräsentieren. Es bietet sich zum Beispiel an, std::vector von geeigneten Strukturen in Grid zu verwenden. Eine Beispieldatei könnte so aussehen: # Das Referenzdreieck Vertices 0.0 0.0 1.0 0.0 # Es folgt e_2: 0.0 1.0 Triangles 1 2 3 EndTriangles Was hier steht, wird ist unspezifiziert und wird ignoriert. Schreiben Sie die Funktion MaxNumEdgesOfVertex (...) , die die maximale Zahl von Kanten liefert, die sich in einem Eckpunkt der Triangulierung treffen. Diese Funktion ist beim Aufstellen der Massenmatrix hilfreich. Eine effiziente Verfahrensweise besteht darin, zu jedem Eckpunkt ein (zunächst leeres) std::set<size_t> S anzulegen. Anschließend besucht man jedes Dreieck und trägt für jeden Eckpunkt seine beiden Nachbar-Eckpunkte in S ein. Die gesuchte Zahl ist dann max{ |S| } + 1. Die Massenmatrix ist die Diskretisierung des L2 (Ω)-Skalarproduktes, (φi , φj )L2 (Ω) = (J(ei ), J(ej ))L2 (Ω) =: hM ei , ej i, i, j = 1, 2, . . . m. Dabei ist h·, ·i das Euklidische Skalarprodukt des Rm . J : Rm → M10 ist die Basisdarstellung der stetigen, 1 stückweise linearen Finiten Elemente bezüglich der nodalen Basis (φi )m i=1 . Die Einträge der Matrix lassen sich effizient berechnen, indem man die Liste der Dreiecke T einmal durchgeht. Man summiert dabei den Beitrag des Integrals über T zu den Matrixeinträgen der Basisfunktionen, die zu den Ecken von T gehören. Dies funktioniert wegen2 Z X (φi , φj )L2 (Ω) = φi φj dx dy. {T |vi ,vj sind Ecken von T } T Die Integrale können mit der Quadraturformel aus Aufgabe 1 c.) exakt bestimmt werden, da der Integrand auf 1 , so wird die Auswertung der Integranden jedem Dreieck quadratisch ist. Wählt man a = 0, b = 83 und c = 24 besonders einfach, denn in den Ecken des Dreiecks ist der Integrand nur 0 oder 1 und im Schwerpunkt tritt stets derselbe Wert auf (welcher?). Denken Sie daran, das Integral über das Referenzdreieck auf das Integral über T zu transformieren: Es muß |T |/|Tref | als Vorfaktor berücksichtigt werden. Als Datenstruktur für die Massenmatrix soll Jagged Diagonal Storage verwendet werden. Denn obwohl M ∈ Rm×m ist, sind maximal c = MaxNumEdgesOfVertex (...) Einträge in jeder Zeile von Null verschieden. Diese Dünnbesetztheit ist bei großen Gittern essentiell. Im JDS-Format nutzt man dies aus, indem man die von Null verschiedenen Einträge der Matrix in jeder Zeile nach links zusammenschiebt. Die von Null verschiedenen Matrixeinträge werden in einem std::valarray<double> coeff der Länge m × c gespeichert. Die Einträge der Zeile i = 1, . . . , m stehen in [coeff[(i − 1) ∗ m], . . . ,coeff[i ∗ m]), wobei nichtverwendete Speicherstellen auf 0 gesetzt werden. Beachten Sie, daß Indizes in C und C++ bei 0 beginnen. Um die richtige Spalte rekonstruieren zu können, wird in einem std::valarray<size_t> col der Länge m × c der um eins verminderte Spaltenindex gespeichert, der zu dem entsprechenden Eintrag in coeff gehört. Nicht verwendete Speicherstellen erhalten m als Spaltenindex. Beispielsweise wird die Matrix 1 0 2 0 0 3 4 0 5 0 M = 0 6 7 0 8 0 0 9 10 0 0 0 0 11 12 so repräsentiert: 1 2 3 4 coeff = 6 7 9 10 11 12 0 5 8 , 0 0 0 0 col = 1 2 3 2 1 2 3 4 5 3 4 5 5 Fassen Sie die Felder und eventuelle Hilfsfunktionen in einer Datenstruktur Matrix zusammen und schreiben Sie den Operator *, der die Matrix-Vektor-Multiplikation ausführt: std::valarray<double> operator* (const Matrix& M, const std::valarray<double>& x) Zur Ausgabe der Matrix M soll ein einfaches Datenformat verwendet werden, das unter anderem in Matlab importiert werden kann: • Zeile 1 enthält die Dimension der Matrix in folgender Gestalt: % mxm q. Dabei ist q die Anzahl der von Null verschiedenen Einträge von M . • Für jeden von Null verschiedenen Eintrag Mi,j der Matrix folgt eine Zeile der Form i j: Mi,j“ (ohne ” Anführungszeichen). Der Matrixeintrag soll mit mindestens 10 signifikanten Stellen ausgegeben werden. Als Anwendung Ihres Finite-Elemente-Codes können Sie die Fläche der zur Verfügung gestellten Triangulierungen berechnen und ausgeben. Stellen Sie dazu die Funktion f ≡ 1 in der nodalen Basis dar, und verwenden Sie |Ω| = (f, f )L2 (Ω) . 1 Zur Erinnerung: Die nodale Basis besteht aus genau den Funktionen in M1 , die in einem Eckpunkt den Wert 1 und in allen 0 anderen den Wert 0 annehmen. 2 Veranschaulichen Sie sich supp(φ φ ). i j Testen und dokumentieren Sie Ihre Komponenten einzeln und im Zusammenspiel. Stellen Sie zum Beispiel die Massenmatrix auf dem Referenzelement manuell auf, und vergleichen Sie das Ergebnis mit Ihrem Programm. Überprüfen Sie ob Ihr M symmetrisch ist und ob der berechnete Flaecheninhalt bei einfachen Beispielen stimmt, etc. (10 Punkte) Abgabe der Programmieraufgabe Lösen Sie die Aufgabe in C++, C oder Fortran. (Wenn Sie nicht C++ verwenden, müssen Sie evtl. auf andere als die oben beschriebenen Datentypen zurückgreifen.) Werfen Sie bis zum Abgabetermin einen mit Matrikelnummer und Name versehenen Ausdruck Ihres Programms in den Einwurfkasten vor Raum 149 im Hauptgebäude. Legen Sie eine elektronische Version Ihrer Lösung auf einer CD oder 3.500 -Diskette bei, die ebenfalls mit Matrikelnummer und Name beschriftet ist, oder schicken Sie sie als Email mit dem Betreff [NAIV] P1 an [email protected]. Gebiete Das Referenzdreieck: 1 0.9 0.8 0.7 0.6 0.5 0.4 0.3 0.2 0.1 0 0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1 Die Programmieraufgabe: 0.8 0.6 0.4 0.2 0 −0.2 −0.4 −0.6 −0.8 −1.5 −1 −0.5 0 0.5 1 Der Pacman: 1 0.8 0.6 0.4 0.2 0 −0.2 −0.4 −0.6 −0.8 −1 −0.8 −0.6 −0.4 −0.2 0 0.2 0.4 0.6 0.8