MEM 253 Αριθμητική Λύση ΜΔΕ * * * 1
Περιγράφουμε το πρόγραμμα fem.py για τη λύση του προβλήματος δύο σημείων (x) + q(x)u(x) = f (x), x [, ], u( ) = u( ) = 0, u x l x r x l x r με τη μέθοδο των πεπερασμένων στοιχείων. Συγκεκριμένα, ο κώδικας fem.py υπολογίζει τη λύση του μεταβολικού προβλήματος V h [, ] ( u, χ h ) + (q u h, χ) = (f, χ) χ V h, όπου είναι ο χώρος των συνεχών, κατά τμήματα γραμμικών συναρτήσεων πάνω από το x l x r. Με h συμβολίζουμε το μέγιστο μήκος των υποδιαστημάτων των διαμερισμών του [, ]. x l x r x l x r q(x) 0 x [, ] Υποθέτουμε ότι q, f C[, ] και για. x l x r 2
Χρησιμοποιούμε τη βιβλιοθήκη numpy για τη διαχείρηση πινάκων και διανυσμάτων, και τη βιβλιοθήκη matplotlib για την απεικόνιση της υπολογιστικής λύσης. import matplotlib.pyplot as plt import numpy as np import math import gaussquad as gq import tridsym as ts def q(x): return 1 def f(x): return x + 12*x**2 - x**4 xl = 0.0; xr = 1.0 Η συνάρτηση gaussquad στο αρχείο gaussquad.py υπολογίζει τους κόμβους και τα βάρη κατάλληλων κανόνων ολοκλήρωσης του Gauss. H συνάρτηση solve από το αρχείο tridsym.py επιλύει τριδιαγώνια συμμετρικά συστήματα εξισώσεων. 3
target = 0.01 eps = 8 * target**2 / (xr - xl) Ο κώδικας επιλέγει (επαναληπτικά) μια διαμέριση του διαστήματος να ισχύει u u h target, όπου target δοσμένη θετική σταθερά. Σύμφωνα με την εκτίμηση [ x l, x r ] έτσι ώστε αρκεί να εξασφαλίσουμε u u h 1 ( 2 2 N i=0 h 2 i r h 2 L 2 ( x i, x i+1 ) ) 1/2 4
N = 39; step = (xr - xl) / (N + 1); x = np.zeros(n+1); for j in range(n+1): x[j] = xl + j*step h = step*np.ones(n+1) e = np.zeros(n+1) m = np.zeros(n+1) Επιλέγουμε αρχικά έναν ομοιόμορφο διαμερισμό του [ x l, x r ] με N εσωτερικούς κόμβους και βήμα step. Για τις ανάγκες του κώδικα χρησιμοποιούμε το διάνυσμα x, για την αποθήκευση του αριστερού άκρου κάθε υποδιαστήματος ( x i, x i+1 ) της διαμέρισης το διάνυσμα h, για την αποθήκευση του μήκους κάθε υποδιαστήματος το διάνυσμα e, για την αποθήκευση των τοπικών σφαλμάτων r h L 2 ( x i, x i+1 ) και το διάνυσμα m, για να σημειώσουμε τα υππδιαστήματα στα οποία το τοπικό σφάλμα είναι μεγαλύτερο της σταθεράς eps. ( x i, x i+1 ) 5
d = np.zeros(n); c = np.zeros(n); b = np.zeros(n) nq, p, w = gq.gaussquad(4) Ο υπολογισμός της λύσης πεπερασμένων στοιχείων απιτεί τη λύση ενός γραμμικού συστήματος με πίνακα = (, ) + (q, ), 1 i, j N, A ij ϕ j ϕ i ϕ j ϕ i και δεξί μέλος b i = (f, ϕ i ), 1 i N. Ο πίνακας A είναι τριδιαγώνιος συμμετρικός. Υπολογίζουμε και αποθηκεύουμε στο διάνυσμα d τα διαγώνια στοιχεία A ii και τα στοιχεία κάτω από τη κύρια διαγώνιο στο διάνυσμα c. Τα στοιχεία του δεξιού μέλους αποθηκεύονται στο διάνυσμα b. {ϕ i } N i=1 V h Εδώ, φυσικά, είναι η συνηθισμένη βάση του χώρου. Η συνάρτηση gaussquad επιστρέφει, στα διανύσματα p και w τους κόμβους και βάρη ενός κανόνα ολοκλήρωσης Gauss με nq σημεία. 6
Το κυρίως μέρος του κώδικα fem.py βρίσκεται μέσα στην (αέναη) ανακύκλωση while True: στην οποία: Κατασκευάζεται ο πίνακας του συστήματος και το δεξί μέλος που αντιστοιχούν στην τρέχουσα διαμέριση του Υπολογίζεται η λύση πεπερασμένων στοιχείων και η εκτίμηση του σφάλματος Αν η εκτίμηση του σφάλματος είναι μικρότερη του target το πρόγραμμα τερματίζεται [ x l, x r ] Αναγνωρίζονται και σημειώνονται τα διαστήματα του διαμερισμού στα οποία ο τοπικός εκτιμητής του σφάλματος είναι μεγαλύτερος της ποσότητας eps Τα διαστήματα που σημειώθηκαν στο προηγούμενο βήμα εκλεπτύνονται εισάγοντας ένα καινούργιο κόμβο στο μέσο τους H διαδικασία επαναλαμβάνεται για τον καινούργιο διαμερισμό 7
Κατασκευή του πίνακα του συστήματος και του δεξιού μέλους με χρήση κανόνα αριθμητικής ολοκλήρωσης for j in range(n+1): xj = x[j]; hj = h[j]; sj = 1/hj; s1 = 0.0; s2 = 0.0; s3 = 0.0 for k in range(nq): qk = q(xj + hj*p[k]) s1 += w[k] * qk * (1 - p[k])**2 s2 += w[k] * qk * (1 - p[k])*p[k] s3 += w[k] * qk * p[k] *p[k] if j > 0: d[j-1] += hj * s1 + sj if j > and j < N: c[j-1] += hj * s2 - sj if j < N: d[j ] += hj * s3 + sj for j in range(n+1): xj = x[j]; hj = h[j]; s1 = 0.0; s2 = 0.0 for k in range(nq): fk = f(xj + hj*p[k]) s1 += w[k] * fk * (1 - p[k]) s2 += w[k] * fk * p[k] if j > 0: b[j-1] += hj * s1 if j < N: b[j ] += hj * s2 8
Υπολογισμός της λύσης πεπερασμένων στοιχείων και εκτίμηση του σφάλματος uh = ts.solve(c, d, b); error = 0.0 for j in range(n+1): xj = x[j]; hj = h[j]; s1 = 0.0 ul = 0; if j > 0: ul = uh[j-1] ur = 0; if j < N: ur = uh[j ] for k in range(nq): qk = q(xj + hj*p[k]) fk = f(xj + hj*p[k]) uk = ul * (1 - p[k]) + ur * p[k] s1 += w[k] * (qk * uk - fk)**2 e[j] = s1 * hj error += e[j] * hj**2 if math.sqrt(error/8) < target: break 9
Αναγνωρίζονται και σημειώνονται τα διαστήματα στα οποία το τοπικό σφάλμα είναι μεγαλύτερο του eps και εκλέπτυνση του διαμερισμού nref = 0 for j in range(n+1): m[j] = 0; if h[j] * math.sqrt(e[j]) > eps: m[j] = 1; nref += 1 newn = N + nref x = np.resize(x, newn+1) h = np.resize(h, newn+1) k = newn for j in range(n, -1, -1): if m[j] == 0: x[k] = x[j]; h[k] = h[j]; k -= 1 else: half = h[j] / 2; mid = x[j] + half x[k] = mid; h[k] = half; k -= 1 x[k] = x[j]; h[k] = half; k -= 1 N = newn c = np.resize(c, N); d = np.resize(d, N); b = np.resize(b, N) for j in range(n): c[j] = 0.0; d[j] = 0.0; b[j] = 0.0 e = np.resize(e, N+1); m = np.resize(m, N+1) for j in range(n+1): m[j] = 0 10
Το πρόγραμμα fem.py επιδέχεται πολλές επεκτάσεις: Υπολογισμός της λύσης πεπερασμένων στοιχείων της εξίσωσης (p(x) u (x)) + q(x)u(x) = f (x), x [ x l, x r ] για κάποια θετική συνάρτηση p(x). Άλλες συνοριακές συνθήκες, Neumann ή Robin, ή μεικτές συνοριακές συνθήκες. Ο κώδικας δεν "αδροποιεί" το διαμερισμό, δηλαδή να αφαιρεί κόμβους του διαμερισμού όταν το τοπικό σφάλμα είναι πολύ μικρό. Ο ενδιαφερόμενος αναγνώστης καλείται να δοκιμάσει την υλοποίηση κάποιων από τις παραπάνω πιθανές επεκτάσεις... 11