W bardzo wielu praktycznych modelach (biologicznych, chemicznych, fizycznych i nawet ekonomicznych) należy rozwiązać równanie różniczkowe postaci
(8.1) | ||||
(8.2) |
Niewiadomą jest funkcja
Ze względu na powszechność występowania takich i podobnych modeli w zastosowaniach matematyki, trudno wyobrazić sobie, by absolwent matematyki stosowanej nie potrafił skutecznie zmierzyć się z takim zadaniem, choćby w najprostszej postaci — wykorzystując w tym celu choćby najprostsze metody. Dlatego w niniejszym rozdziale zajmiemy się szczegółowo rozwiązaniem takiego zadania w Octave.
W ogólnym przypadku, moglibyśmy mieć do czynienia z macierzą współczynników
Współczynnik dyfuzji jest dodatnią funkcją skalarną,
Zaczniemy więc od samodzielnej dyskretyzacji równania (8.1) w najprostszym przypadku.
Na początek przyjmijmy, że warunek brzegowy jest jednorodny:
(8.3) | ||||
(8.4) |
Najprostszą, choć zazwyczaj najmniej skuteczną (przez swój brak wyrafinowania i dostosowania do faktycznego przebiegu rozwiązania) metodą dyskretyzacji takiego równania jest użycie równomiernej siatki na
Gdy
(8.5) | ||||
(8.6) |
Aby znaleźć jego przybliżone rozwiązanie, możemy na przykład zastąpić równanie różniczkowe odpowiadającym mu równaniem różnicowym,
(8.7) | ||||
(8.8) |
gdzie
Jest to oczywiście równanie liniowe na współczynniki
gdzie
(8.9) |
Ponieważ interesują nas dobre przybliżenia (to znaczy przypadek, gdy sparse
, ale ze względu na to, że nasza konkretna macierz ma wyjątkowo prostą strukturę, użyjemy polecenia
hx = X/(Nx+1); Tx = spdiags( repmat([-1 2 -1]/(hx^2), Nx, 1), [-1,0,1], Nx, Nx);gdzie wcześniej musimy zdefiniować
X
Nx
repmat
klonuje daną macierz Nx
razy.
Dzięki reprezentacji naszej macierzy w formacie rzadkim, macierz rozmiaru
Spróbuj zaimplementować obliczanie
[-1 2 -1]/(hx^2)przez pozornie ,,równie dobrze wyglądające”
[-1 2 -1]/hx*hxCzy dostajesz tę samą macierz
Tx
? To jeszcze jeden przyczynek do tego, ja ważne jest, by na różne sposoby zweryfikować poprawność konkretnej implementacji.
Otrzymana powyżej macierz jest trójdiagonalna (a przy tym: symetryczna i dodatnio określona), zatem właściwie zrealizowany algorytm eliminacji Gaussa (lub lepiej: rozkładu \
” — sprawdza na wstępie, czy macierz układu jest właśnie rozrzedzona, a jeśli tak — stosuje odpowiedni solver oparty na eliminacji Gaussa, w naszym przypadku w pełni uwzględniający trójdiagonalną strukturę. Dlatego rozwiązanie naszego układu skutecznie i szybko obliczymy komendą
Ux = Tx \ Fx;
Wyznacz przybliżone rozwiązanie równania
gdzie
Dzieląc równanie przez stałą
Najpierw musimy więc zdefiniować podstawowe obiekty wykorzystywane w procesie dyskretyzacji.
X = 2; % fizyczne rozmiary obszaru k = 2.45; % stała dyfuzji Nx = 199; hx = X/(Nx+1); px = hx*[0:Nx+1]'; % prawdziwe współrzędne węzłów dyskretyzacji % potrzebne m.in. do definicji prawej strony; % określone łącznie z brzegiem obszaru Tx = lap1ddset(Nx,hx); % macierz laplasjanu % wyznaczamy funkcję prawej strony m = 1; % punkt graniczny pxl = px(px<m); pxr = px(px>=m); % węzły na lewo i prawo od "m" Fx = [1./(2-pxl); 1./(pxr.^5)] / k; plot(px,Fx); % sprawdzamy wizualnie poprawność Fx Fx = Fx(2:end-1); % wybieramy tylko wartości w wewnętrznych węzłach
Zwróćmy uwagę na sposób generowania prawej strony: ponieważ musimy zdefiniować ją jedynie w wewnętrznych węzłach siatki, odwołujemy się jedynie do węzłów Fx(2:end-1)
. Dla większej skuteczności (i przejrzystości) kodu użyliśmy operatora potęgowania tablicowego.
Do zdefiniowania macierzy lap1ddset
,
function T = lap1ddset(N, h) T = spdiags( repmat([-1 2 -1]./(h^2), N, 1), [-1,0,1], N, N); end
Teraz wystarczy rozwiązać równanie i wizualizować wynik:
Ux = Tx \ Fx; plot(px, [0; Ux; 0]); legend('U_{N_x}(x)'); grid on;
Pozostaje przekonać się, czy uzyskane przez nas rozwiązanie jest wystarczająco dokładne. W tym celu zastosujemy inżynierski sposób: sprawdzimy, jak bardzo różni się nasze rozwiązanie od rozwiązania uzyskanego na gęstszej siatce.
Zapamiętamy więc Ux
pod tymczasową nazwą,
TUx = Ux;
i ponownie uruchomimy symulację, startując jednak z
octave:12> norm(Ux(2:2:end-1)-TUx,Inf) ans = 9.5256e-06
Zatem rozwiązanie uzyskane na dwukrotnie zagęszczonej siatce (a więc zapewne dokładniejsze, bo błąd aproksymacji maleje jak
Nasza praca nie będzie zakończona, dopóki nie zweryfikujemy na kilku testowych przykładach, czy wykorzystana metoda produkuje rezultaty zgodne z oczekiwaniami. Minimalny zestaw testów mógłby tutaj wyglądać następująco:
Czy
Czy
Ile wynosi
Czy jeśli
Funkcja
Wykonanie tych testów pozostawiamy Czytelnikowi.
Przeprowadź testy poprawności implementacji zarówno macierzy
Aby na przykład sprawdzić wynik działania operatora
Ux = ones(size(Fx)); s = Tx*Ux; norm(s(2:end-1),Inf) plot(px,[0;s;0]); grid on;Obliczona powyżej norma fragmentu
s
powinna być równa zero, a wykres powinien mieć dwa piki w punktach tuż przy krańcach odcinka. Oczywiście, nie jest głupim pomysłem sprawdzenie wyniku dla kilku (np. małej i dużej) wartości Chcąc rozwiązać dwuwymiarowe zadanie Poissona,
(8.10) | ||||
(8.11) |
postąpimy analogicznie jak w poprzednim przypadku, korzystając z jednorodnej siatki węzłów na prostokącie
Będziemy zatem mieli do czynienia z wartościami rozwiązania w punktach
przy czym
Wartości rozwiązania dyskretnego
Aby znaleźć przybliżone rozwiązanie (8.10), równanie różniczkowe zastąpimy odpowiadającym mu równaniem różnicowym, korzystając z tego samego co poprzednio sposobu aproksymacji drugiej pochodnej:
i analogicznie dla
(8.12) | ||||
(8.13) |
gdzie
(zwróćmy uwagę na to, że ,,fizyczne” współrzędne są odwrócone: wartości odpowiadające kolejnym współrzędnym w kierunku osi \
” rozwiązywania układów równań liniowych), nasze zadanie musimy sformułować w postaci, w której niewiadome (a także wektor prawej strony) są reprezentowane w postaci wektora, długości
(8.14) |
Analogicznie zdefiniowalibyśmy także wektor prawej strony
gdzie
(8.15) |
W kolejnych diagonalnych blokach
Przypomnijmy na marginesie, że iloczyn tensorowy macierzy
W Octave istnieje funkcja kron
, która wyznacza iloczyn Kroneckera macierzy, przy czym zastosowana do macierzy rozrzedzonej daje w rezultacie także macierz rozrzedzoną. Dlatego definicja macierzy
hx = X/(Nx+1); hy = Y/(Ny+1); Tx = lap1ddset(Nx, hx); Ix = speye(Nx, Nx); Ty = lap1ddset(Ny, hy); Iy = speye(Ny, Ny); P = kron(Iy, Tx) + kron(Ty, Ix);
Zweryfikuj poprawność swojej implementacji macierzy
W przypadku macierzy \
”, potrafi (czasem!) uporządkować niewiadome i równania tak, żeby wyraźnie obniżyć koszt rozwiązania.
Zatem, moglibyśmy w dalszym ciągu wyznaczać rozwiązanie poleceniem
U = P \ F;o ile tylko
F
. Uzyskane rozwiązanie, U
, będzie dla nas z pewnością bardziej użyteczne, jeśli ,,zwiniemy” je z powrotem do postaci macierzowej:
U = reshape(U,Nx,Ny)
.
Jak to powinno wyglądać w szczegółach, jest treścią poniższego ćwiczenia.
Wyznacz przybliżone rozwiązanie równania dyfuzji w środowisku anizotropowym,
gdzie
Narysuj wykres
Zgodnie z wyżej opisanym schematem, musimy wyznaczyć macierz
(zwróćmy uwagę na obecność współczynnika
Reszta jest już tylko (?) programowaniem. Musimy przy tym pamiętać, by we właściwym momencie z dwuwymiarowej tablicy wartości rozwiązania/prawej strony przechodzić do postaci rozwiniętej w wektor (lub na odwrót).
X = 2; Y = 3; k = 2.45; Nx = 100; Ny = 200; hx = X/(Nx+1); hy = Y/(Ny+1); Tx = lap1ddset(Nx, hx); Ix = speye(Nx, Nx); Ty = k*lap1ddset(Ny, hy); Iy = speye(Ny, Ny); P = kron(Iy, Tx) + kron(Ty, Ix);
Chwila refleksji Jest jeszcze jeden ważny szczegół: funkcja prawej strony nie jest ciągła, zatem dokładne rozwiązanie nie może być klasy
Ten dylemat natury teoretycznej wydaje się być nieistotnym dla praktyka — w końcu przecież wystarczy użyć naszego sposobu i sprawdzić, ,,czy sobie poradzi”. Jednak kłopot polega na tym, że nie zawsze mamy wystarczające podstawy, by rozstrzygnąć, czy otrzymane rozwiązanie jest ,,sensowne”. Użycie kryterium czysto estetycznego (,,ładnie wygląda”) niestety często prowadzi do pochopnej akceptacji całkowicie fałszywych wyników.
Zatem wstępne zastanowienie się nad jakością używanych narzędzi aproksymacji problemu nie jest bez znaczenia, bo użycie błędnej/niewłaściwej dyskretyzacji może w efekcie doprowadzić nas do efektownych wizualizacji, które nie będą miały wiele wspólnego z realnym rozwiązaniem. Zachęcając Czytelnika do poszukania odpowiedzi18Okazuje się, że nasza dyskretyzacja ma sens nawet w tym przypadku: gdy potraktować ją jako dyskretyzację metodą elementu skończonego z nienajlepszą (ale wciąż sensowną) aproksymacją wektora prawej strony. Następnym etapem rozwiązywania tego zadania byłoby więc ,,podrasowanie” wektora prawej strony. na to pytanie na przykład w naszym ulubionym rozdziale o metodzie różnic skończonych dla równań eliptycznych w wykładzie z numerycznych równań różniczkowych, teraz — na własne ryzyko — (a przy tym dla lepszego rozeznania) sprawdzimy jednak po prostu, ,,co wyjdzie”.
Na początek wygodnie będzie nam utworzyć macierz
px = hx*[0:Nx+1]'; % prawdziwe współrzędne węzłów dyskretyzacji py = hy*[0:Ny+1]'; [pX, pY] = meshgrid(px,py); % tablica węzłów do manipulacji tablicowej F = zeros(size(pX)); [i,j] = find( (abs(pX-1) <= 0.5) & (abs(pY-2) <= 0.5) ); F(i,j) = 1; F = permute(F, [2 1]); % zamienia ze sobą wiersze i kolumny
Ostatni krok może wydać się nam niejasny: dlaczego musimy zamienić ze sobą wiersze i kolumny macierzy pX
, pY
, które Octave generuje poleceniem meshgrid
. Aby ułatwić wizualizację takich macierzy, indeksy odpowiadające zmiennej F
na podstawie pX
i pY
, dostajemy F
o zamienionych ze sobą wierszach i kolumnach. Dlatego na koniec musimy to odwrócić poleceniem permute
19Tak, moglibyśmy też użyć transpozycji, ale nie uogólniłoby się to na przypadek trójwymiarowy, którym za niedługo będziemy się zajmować..
Chcąc teraz wizualizować macierz F
, musimy ponownie zamienić ze sobą wymiary: polecenie imagesc
oczekuje macierzy wartości w węzłach takiej, jak np. produkowana poleceniem meshgrid
.
imagesc(px, py, permute(F, [2 1])); axis('xy','square'); title('Prawa strona');
Na zakończenie musimy z tablicy F
usunąć niepotrzebne wartości odpowiadające węzłom leżącym na brzegu, a następnie ,,rozprostować” F
do wektora:
F = F(2:Nx+1,2:Ny+1); F = F(:);Ostatnie polecenie tworzy wektor, którego elementy są kolejnymi elementami kolejnych kolumn macierzy
F
— dokładnie tak, jak umawialiśmy się w (8.14).
Teraz wystarczy zadanie rozwiązać, a wynik na powrót zwinąć do tablicy i wizualizować.
U = zeros(Nx+2,Ny+2); U(2:Nx+1, 2:Ny+1) = reshape(P \ F, Nx, Ny); imagesc(px, py, permute(U, [2 1])); axis('xy','square'); title('Rozwiazanie');
Na zakończenie powinniśmy ocenić (w analogiczny sposób jak w przypadku jednowymiarowym), jakiego rzędu błąd popełniliśmy stosując naszą dyskretyzację. Wykonanie odpowiednich testów pozostawiamy Czytelnikowi.
Czytelnik zechce sam sprawdzić, że dyskretyzacja operatora Laplace'a w kostce
(8.16) |
dla
Aby wciąż korzystać ze standardowych narzędzi Octave jak w przypadkach dwu- i jednowymiarowym, nasze zadanie musimy sformułować w postaci, w której niewiadome tworzą jeden wektor długości
Będziemy szukać wektora
gdzie
(8.17) |
Implementacja takiej macierzy, a nastepnie jej rozwiązanie za pomocą standardowego bezpośredniego solvera z Octave przebiega w sposób w pełni analogiczny do przypadku dwuwymiarowego. Niestety, gdy ledwie dziesięciokrotnie zmniejszymy rozmiar oczka siatki w każdym kierunku, liczba niewiadomych rośnie aż tysiąckrotnie. Otrzymane zadanie będzie więc bardzo duże: faktycznie, już dla umiarkowanego
Rozwiąż zagadnienie Poissona
gdzie
Tak sformułowane zadanie ma charakter czysto jakościowy, a nie ilościowy — rzeczywiście, skoro
pokazać wygładzający efekt dyfuzji
zobaczyć ograniczenia bezpośredniego solvera układów równań z macierzami rzadkimi na jakimkolwiek konkretnym przykładzie.
X = 1; Y = 2; Z = 3; % fizyczne rozmiary kostki Nx = 30; Ny = 30; Nz = 30; hx = X/(Nx+1); hy = Y/(Ny+1); hz = Z/(Nz+1); px = hx*[0:Nx+1]; py = hy*[0:Ny+1]; pz = hz*[0:Nz+1]; % prawdziwe współrzędne węzłów (lacznie z brzegiem obszaru) % macierze pomocnicze dla zadan jednowymiarowych Tx = lap1ddset(Nx,hx); Ty = lap1ddset(Ny,hy); Tz = lap1ddset(Nz,hz); Ix = speye(size(Tx)); Iy = speye(size(Ty)); Iz = speye(size(Tz)); % macierz laplasjanu S = kron(Iz, kron(Iy,Tx)) + kron(Iz, kron(Ty, Ix)) + kron(Tz, kron(Iy, Ix)); % prawa strona i jej wizualizacja F = 1 + 0.1*(2*rand(Nx+2,Ny+2,Nz+2)-1); slice(px, py, pz, permute(F, [2 1 3]), X/2,Y/2, Z/2); xlabel('x'); ylabel('y'); zlabel('z'); grid on; title('Prawa strona'); pause(1); F = F(2:Nx+1,2:Ny+1,2:Nz+1); % ograniczamy się do węzłów wewnętrznych % wyznaczamy rozwiązanie U = zeros(Nx+2,Ny+2,Nz+2); U(2:Nx+1,2:Ny+1,2:Nz+1) = reshape(S \ F(:),Nx,Ny,Nz); % wizualizacja rozwiązania slice(px, py, pz, permute(U, [2 1 3]), X/2,Y/2, Z/2); xlabel('x'); ylabel('y'); zlabel('z'); grid on; title('Rozwiazanie'); pause(1);
Zwróćmy uwagę na to, jak grubą siatkę wzięliśmy w naszym przykładowym kodzie. Gdyby bowiem zagęścić ją dwukrotnie, czas potrzebny na uzyskanie rozwiązania znacząco wydłuża się; próba wyznaczenia rozwiązania na siatce
Jakkolwiek stosowany przez nas solver bezpośredni całkiem dobrze sobie radzi z zadaniami dwuwymiarowymi, w których liczba węzłów sięga nawet
W przypadku tak specyficznej macierzy, jaką jest macierz laplasjanu, można na szczęście wskazać specjalne metody jej rozwiązywania. To typowa i charakterystyczna dla obliczeń naukowych sytuacja, kiedy do konkretnego zadania można dobrać niestandardową metodę, która będzie znacznie skuteczniejsza od metod ogólnego przeznaczenia. Często taka metoda będzie motywowana fizycznymi chechami zadania; w naszym przypadku lepsza metoda będzie wynikać wyłącznie z matematycznych własności zadania.
Otóż okazuje się, że znane są jawne wzory na czynniki rozkładu macierzy
skąd wynika, że
w przypadku dwuwymiarowym:
w przypadku trójwymiarowym:
Rozwiązanie układu z macierzą diagonalną kosztuje tylko
Pokażemy, jak rozwiązywać trójwymiarowe równanie Poissona (por. ćwiczenie 8.6) z wykorzystaniem solvera opartego na FFT. Jest to niewielka modyfikacja kodu z ćwiczenia 8.6: niepotrzebne fragmenty zostały wykomentowane, a nowo wstawiane części oznakowano komentarzem NOWE
.
X = 1; Y = 2; Z = 3; % fizyczne rozmiary kostki Nx = 100; Ny = 100; Nz = 100; scale = 0.1; hx = X/(Nx+1); hy = Y/(Ny+1); hz = Z/(Nz+1); px = hx*[0:Nx+1]; py = hy*[0:Ny+1]; pz = hz*[0:Nz+1]; % prawdziwe wspolrzedne wezlow (lacznie z brzegiem obszaru) % macierze pomocnicze dla zadan jednowymiarowych % Tx = lap1ddset(Nx,hx); Ty = lap1ddset(Ny,hy); Tz = lap1ddset(Nz,hz); % potrzebne tylko dla metody bezposredniej Ix = speye(Nx,Nx); Iy = speye(Ny,Ny); Iz = speye(Nz,Nz); % NOWE! Lx = spdiags(fftsetd(Nx,hx),0,Nx,Nx); Ly = spdiags(fftsetd(Ny,hy),0,Ny,Ny); Lz = spdiags(fftsetd(Nz,hz),0,Nz,Nz); % macierze diagonalne, potrzebne tylko dla metody FFT % macierz laplasjanu, tylko dla metody bezposredniej % S = kron(Iz, kron(Iy,Tx)) + kron(Iz, kron(Ty, Ix)) + kron(Tz, kron(Iy, Ix)); % NOWE! % macierz diagonalna zadania 3D, tylko dla metody FFT global Lambda; % gloablna, bo wykorzystywana przez fftsolve3dd() Lambda = kron(Iz, kron(Iy,Lx)) + kron(Iz, kron(Ly, Ix)) + kron(Lz, kron(Iy, Ix)); % prawa strona F = 1 + scale*(2*rand(Nx+2,Ny+2,Nz+2)-1); slice(px, py, pz, permute(F, [2 1 3]), X/2,Y/2, Z/2); xlabel('x'); ylabel('y'); zlabel('z'); grid on; title('Prawa strona'); pause(1); F = F(2:Nx+1,2:Ny+1,2:Nz+1); % tylko dla metody bezposredniej % U = zeros(Nx+2,Ny+2,Nz+2); % U(2:Nx+1,2:Ny+1,2:Nz+1) = reshape(S \ F(:),Nx,Ny,Nz); % rozwiazanie % NOWE! % macierz diagonalna zadania 3D, tylko dla metody FFT U = zeros(Nx+2,Ny+2,Nz+2); tic; U(2:Nx+1,2:Ny+1,2:Nz+1) = fftsolve3dd(F); toc % rozwiazanie FFT % wizualizacja slice(px, py, pz, permute(U, [2 1 3]), X/2,Y/2, Z/2); xlabel('x'); ylabel('y'); zlabel('z'); grid on; title('Rozwiazanie'); pause(1);
Powyższy skrypt korzysta z pewnych dodatkowych funkcji: fftsolve3dd
i fftsetd
; poniżej ich listingi.
function V = fftsolve3dd(F) % Rozwiazuje, korzystajac z FFT, uklad rownan liniowych Lap*V = F, % gdzie V,F sa macierzami wartosci na siatce kwadratowej. % Warunki brzegowe Dirichleta: jednorodne. global Lambda; % wartosci wlasne [Nx, Ny, Nz] = size(F); F = fftmult3d(F); %F = ZX*F*ZY; V = Lambda\F(:); V = reshape(V, Nx, Ny, Nz); V = fftmult3d(V); %V = ZX*V*ZY; end
function Lambda = fftsetd(N,h) % % Wartosci wlasne dla 1-d Laplasjanu z war brzeg. Dirichleta Lambda = (4/h^2)*(sin(0.5*pi*[1:N]'/(N+1))).^2; % exact eigenvalues end
Nagle… realne staje się szybkie wyznaczenie rozwiązania zadania trójwymiarowego, nie tylko na siatce
Kluczowa dla szybkości działania solvera jest funkcja fftsolve3dd
, wykorzystująca umiejętność Octave przykładania FFT ,,wzdłuż” zadanego wymiaru macierzy.
Zmodyfikuj powyższy kod oraz funkcję fftsolve3dd
tak, by mogła działać w przypadku dyskretyzacji dwuwymiarowego równania Poissona.
Sprawdź, czy metoda iteracyjna — taka jak na przykład PCG, o której więcej dowiesz się z wykładu z Matematyki Obliczeniowej II, a która jest implementowana w Octave komendą pcg
— jest rozsądną alternatywą dla solvera FFT.
W przypadku zadania trójwymiarowego należy użyć komendy
U = zeros(Nx+2,Ny+2,Nz+2); [Uin, flag, relres, iter, resvec, eigest] = ... pcg(S,F(:), (1e-3)*min([hx,hy,hz])^2, 50); U(2:Nx+1,2:Ny+1,2:Nz+1) = reshape(Uin,Nx,Ny,Nz); disp(['Uwarunkowanie S = ', num2str(eigest(2)/eigest(1))]);Jakie jest uwarunkowanie tej macierzy? (Odpowiedź na to pytanie znajdziesz na przykład znów w wykładzie z Matematyki Obliczeniowej II.) Co to oznacza dla solvera iteracyjnego, takiego jak PCG?
Wykorzystywany przez nas solver FFT ma niestety poważne ograniczenia, które uniemożliwiają zastosowanie go wprost do zadania (8.1), w którym mamy co prawda do czynienia z jednorodną siatką na kostce
Okazuje się wszakże, iż możemy efektywnie skorzystać z FFT nawet w przypadku zmieniającego się współczynnika dyfuzji. Jednak wtedy musimy podejść do zadania z jeszcze innej strony: używając metody iteracyjnej PCG do rozwiązywania zadania i wykorzystując jednocześnie bezpośredni solver oparty na FFT w charakterze wydajnego operatora ściskającego21Więcej szczegółów na temat PCG, ściskania i macierzy ściskających można poznać na wykładzie z Matematyki Obliczeniowej II., dzięki czemu metoda PCG będzie szybko zbieżna!
Zwróćmy uwagę, że wiele metod iteracyjnych — w tym metoda PCG — rozwiązywania układu równań liniowych
nie wymaga wcale konstrukcji macierzy
Aby przetestować nasze nowe podejście, zaczniemy od przypadku jednowymiarowego, uwzględniając (wreszcie!) przypadek, gdy warunek brzegowy nie jest jednorodny.
Gdy
(8.18) | ||||
(8.19) |
Aby znaleźć jego przybliżone rozwiązanie, możemy na przykład zastąpić równanie różniczkowe odpowiadającym mu równaniem różnicowym. Okazuje się, że prostoduszne
dość kiepsko aproksymuje nasze równanie, a znacznie lepsze własności (wiedza nie szkodzi!) ma aproksymacja postaci
(8.20) | ||||
(8.21) |
gdzie za
gdzie, zgodnie z intuicją,
W naszym zadaniu poszukujemy rozwiązania postaci
takiego, że
Powyższe zadanie, z niejednorodnym warunkiem brzegowym, bardzo łatwo sprowadzić do zadania z warunkiem jednorodnym. Rzeczywiście, oznaczając
mamy, że
(8.22) |
W ten sposób wystarczy zmodyfikować prawą stronę równania, by potem przez rozwiązanie równania z jednorodnym warunkiem brzegowym wyznaczyć wartości rozwiązania we wszystkich punktach wewnętrznych siatki.
Oto więc, jak można byłoby zaimplementować działanie operatora
function F = multdiff1dd(Uin, h, A, U) % % wyznacza F = -(a(x) u'(x))', dla dyskretyzacji % % Uin - wartości u(x) we wnętrzu siatki % h - długość kroku siatki % A - wartości a(x) we wszystkich węzłach pośrednich siatki % domyślnie a(x) = 1 % U - wartości zerowego rozszerzenia warunku brzegowego na wszystkie węzły siatki % domyślnie U(x) = 0 if nargin < 4 % domyślnie jednorodny warunek brzegowy U = zeros(length(Uin)+2,1); end [Nx, Ny] = size(U); if nargin < 3 % domyślnie a(x) = 1 Ax = ones(Nx-1,1); else Ax = A; end ix = 2:Nx-1; % indeksy odpowiadajace wartosciom z wnetrza U(2:Nx-1) = Uin; F = (-U(ix-1).*Ax(ix-1) ... + U(ix).*(Ax(ix-1)+Ax(ix)) ... - U(ix+1).*Ax(ix))/(h(1)^2); end
Jeśli więc chcemy wyznaczyć wynik działania
tablicy U
o wartościach zadanych wyłącznie w wewnętrznych węzłach siatki i domyślnej wartości zero na brzegu, użylibyśmy wywołania
F = multdiff1dd(U, hx, Ax);
tablicy o wartościach w wewnętrznych węzłach siatki równej zero i wartościach na brzegu określonych przez tablicę G
(rozmiaru
F = multdiff1dd(zeros(Nx,1), hx, Ax, G);
Ostatnie wywołanie pasuje jak ulał do wyznaczenia poprawki na prawą stronę
Pamiętając o konieczności nieustannego weryfikowania poprawności zaimplementowanych funkcji, przygotuj i przeprowadź serię testów sprawdzających funkcję multdiff1dd
.
Warto wykorzystać w tym celu m.in. napisane wcześniej funkcje testujące/rozwiązujące równanie Poissona z jednorodnym warunkiem brzegowym.
Zgodnie z wcześniej przyjętą strategią, zastosujemy iteracyjną metodę rozwiązywania równania. Dla ustalenia uwagi przyjmieny, że
A = pX+1; % definicja wartości współczynnika dyfuzji w węzłach siatki Ax = 0.5*(A(1:end-1) + A(2:end)); multdiff = @(x) multdiff1dd(x, hx, Ax); % funkcja przykładająca operator dyfuzji z zerowym warunkiem brzegowym precond = @(x) Tx\x; % jako operator ściskający wybieramy rozwiązanie r-nia Poissona F = ones(Nx,1); U = ones(Nx+2,1); F = F - multdiff1dd(zeros(Nx,1), hx, Ax, U); % uwzględnienie warunku brzegowego [Uin, flag, relres, iter, resvec, eigest] = ... pcg(multdiff, F(:), (1e-3)*hx^2, 50, precond); U(2:Nx+1) = Uin;
Czy w przypadku jednowymiarowej dyfuzji nie można byłoby w prosty sposób użyć solvera bezpośredniego?
Faktycznie, mamy
A więc można (i nawet: trzeba!) w tym wypadku użyć solvera bezpośredniego. My implementujemy solver iteracyjny jedynie w celu oswojenia i sprawdzenia podejścia związanego z wykorzystaniem metody iteracyjnej…
Pamiętając o konieczności nieustannego weryfikowania poprawności zaimplementowanych funkcji, przygotuj i przeprowadź serię testów sprawdzających opracowaną metodę.
Przede wszystkim, warto sprawdzić, co dzieje się w przypadku
Następnie należy koniecznie przeprowadzić serię testów dla
Funkcja mnożąca wektor przez operator ściskający powinna być możliwie tania, gdyż będziemy ją wywoływać wiele razy. Nasza funkcja:
precond = @(x) Tx \ x;ma tę wadę, że za każdym wywołaniem dokonuje rozkładu LU macierzy
Wyznaczając na etapie preprocessingu rozkład LU (lub Cholesky'ego)
Rx = spchol(Tx); % rozkład Cholesky'ego: Rx'*Rx = Tx precond = @(x) Rx \ (Rx' \ x);
Gdybyśmy od razu chcieli atakować zadanie (8.1) w przypadku dwu- lub więcejwymiarowym, mogłoby nam być dosyć trudno. Teraz jednak, po przepracowaniu przypadku jednowymiarowego, sprawa powinna potoczyć się wartko. Będziemy musieli pamiętać o dwoistości spojrzenia na niewiadome: raz będziemy je traktować jako (dwuwymiarową!) tablicę wartości w węzłach (tak będzie nam wygodniej zapisać działanie operatora dyfuzji), innym razem będziemy musieli rozwinąć tablicę w jeden długi wektor (tak działa funkcja pcg
w Octave).
Przez analogię do przypadku jednowymiarowego, nasza dyskretyzacja będzie miała postać:
z warunkiem brzegowym
Mamy do wyznaczenia
Zauważmy, że aby na przykład wyznaczyć iloraz różnicowy
nie musimy używać żadnej macierzy; możemy po prostu wykonać działanie, które, nieco nadużywając notacji Octave22W Octave nie wolno indeksować macierzy od zera., zapiszemy
F = -U( 0:Nx-1, :) + 2*U( 1:Nx, :) - U( 2:Nx+1, :); F = F/hx^2;Analogicznie, dysponując tablicą
Ax
zawierającą wartości wyznaczymy korzystając z operatorów tablicowego mnożenia,
F = -Ax(0:Nx-1).*U( 0:Nx-1, :) + (Ax(0:Nx-1)+Ax(1:Nx)).*U( 1:Nx, :) - Ax(1:Nx).*U( 2:Nx+1, :); F = F/hx^2;
Powyższa obserwacja pozwoli nam efektywnie — bez użycia pętli i instrukcji warunkowych — obliczyć wynik działania dyskretnego dwuwymiarowego operatora dyfuzji na tablicy wartości funkcji siatkowej w węzłach.
Zaimplementuj funkcję F = multdiff2dd(Uin, h, Ax, Ay, U)
, będącą analogonem opracowanej wcześniej funkcji jednowymiarowej.
Niech
Najpierw implementacja operatora dyfuzji:
function F = multdiff2dd(Uin, h, A, U) % % wyznacza F = -div ( a(x,y) grad u(x,y) ), dla dyskretyzacji % % Uin - wartości u(x,y) we wnętrzu siatki % h - długość kroku siatki: h = [hx,hy] % A - wartości a(x,y) we wszystkich punktach pośrednich siatki: A = {Ax, Ay} % domyślnie a(x) = 1 % U - wartości zerowego rozszerzenia warunku brzegowego na wszystkie węzły siatki % domyślnie U(x) = 0 if nargin < 4 % domyślnie jednorodny warunek brzegowy U = zeros(size(Uin)+2); end [Nx, Ny] = size(U); if nargin < 3 % domyślnie a(x) = 1 Ax = ones(Nx-1,Ny-2); Ay = ones(Nx-2,Ny-1); else Ax = A{1}; Ay = A{2}; end ix = 2:Nx-1; iy = 2:Ny-1; % indeksy odpowiadajace wartosciom z wnetrza U(2:Nx-1,2:Ny-1) = Uin; F = (-U(ix-1,iy).*Ax(ix-1,:) + U(ix,iy).*(Ax(ix-1,:)+Ax(ix,:)) - U(ix+1,iy).*Ax(ix,:))/(h(1)^2) + ... (-U(ix,iy-1).*Ay(:,iy-1) + U(ix,iy).*(Ay(:,iy-1)+Ay(:,iy)) - U(ix,iy+1).*Ay(:,iy))/(h(2)^2); endPodczas pisania tej funkcji musimy bardzo uważać, by uniknąć pomyłek przy przesunięciach indeksów. Zwróćmy także uwagę na pewną rozrzutność w gospodarowaniu pamięcią: wektory pomocnicze
Ax
i Ay
są (formalnie niepotrzebnymi) kopiami pól struktury A
: użyliśmy ich dla większej czytelności kodu.
Dalej, po podstawowych testach (które tutaj pominiemy wierząc, że Czytelnik samodzielnie i rutynowo je przeprowadzi), wykorzystamy świeżo napisaną funkcję w konkretnym przypadku opisanym w zadaniu.
X = 2; Y = 3; Nx = 151; Ny = 153; hx = X/(Nx+1); hy = Y/(Ny+1); px = hx*[0:Nx+1]; py = hy*[0:Ny+1]; [pX, pY] = meshgrid(px,py); A = pX.*pY+1; contour(px, py, A ); title('A(x,y)'); pause(1); A = permute(A, [2 1]); % przechodzimy na nasz układ tablicowy Ax = 0.5*(A(1:end-1,2:end-1) + A(2:end,2:end-1)); Ay = 0.5*(A(2:end-1,1:end-1) + A(2:end-1,2:end)); U = permute(pX.*pY, [2 1]); F = multdiff2dd(U(2:Nx+1,2:Ny+1),[hx,hy],{Ax,Ay},U); contour(px(2:Nx+1), py(2:Ny+1), permute(F, [2 1]) ); title('-div (a(x,y) grad u(x,y))'); pause(1);
Sama procedura rozwiązania równania nie zmieni się zanadto w stosunku do przypadku jednowymiarowego; dla ustalenia uwagi przyjmiemy, że
% % definicje siatki oraz macierzy Lx, Ly, Ix, Iy jak w przypadku r-nia Poissona - % - tutaj pominięte! % global Lambda; Lambda = kron(Iy,Lx) + kron(Ly,Ix); % definicja wartości współczynnika dyfuzji w węzłach siatki [pX, pY] = meshgrid(px,py); A = pX.*pY+1; A = permute(A, [2 1]); Ax = 0.5*(A(1:end-1,2:end-1) + A(2:end,2:end-1)); Ay = 0.5*(A(2:end-1,1:end-1) + A(2:end-1,2:end)); % definicja operatorów niezbędnych do rozwiązania zadania multdiff = @(x) reshape(multdiff2dd(reshape(x,Nx,Ny), [hx,hy], {Ax,Ay}), Nx*Ny, 1); % funkcja przykładająca operator dyfuzji precond = @(x) reshape(fftsolve2dd(reshape(x, Nx, Ny)), Nx*Ny, 1); % jako operator ściskający wybieramy rozwiązanie r-nia Poissona metodą FFT F = ones(Nx,Ny); U = ones(Nx+2,Ny+2); F = F - multdiff2dd(zeros(size(F)), [hx,hy], {Ax,Ay}, U); % uwzględnienie warunku brzegowego [Uin, flag, relres, iter, resvec, eigest] = ... pcg(multdiff, F(:), (1e-3)*min([hx,hy])^2, 50, precond); U(2:Nx+1,2:Ny+1) = reshape(Uin,Nx,Ny); contour(px, py, permute(U, [2 1]) ); xlabel('x'); ylabel('y');
Brakującą funkcję fftsolve2dd
bez trudu opracujesz, mając za wzór funkcję fftsolve3dd
z przykładu 8.1.
Zaimplementuj w Octave funkcję rozwiązującą trójwymiarowe zadanie dyfuzji.
Zaimplementuj w Octave funkcję rozwiązującą na kostce
W przypadku zadań trudniejszych, na przykład obszarów o bardziej skomplikowanej geometrii, warto skorzystać z bardziej wyspecjalizowanych narzędzi. Jedną z możliwości jest użycie biblioteki Deal.II napisanej w języku C++ i pozwalającej dyskretyzować równania różniczkowe różnych typów przy użyciu metody elementu skończonego. Zawiera on w sobie także kilka narzędzi do rozwiązywania zadań dyskretnych; ma także możliwość współpracy z innymi pakietami pomocniczymi, takimi jak zestaw procedur rozwiązywania zadań dyskretnych na komputerach wieloprocesorowych PETSc. Wadą tego podejścia jest konieczność nauczenia się i zrozumienia tej skomplikowanej biblioteki, co jest bardzo czasochłonne.
Tej wady nie mają proste narzędzia, takie jak program freeFEM++, ale ich stosowalność jest mocno ograniczona.
Zaimplementuj w C/C++ funkcję rozwiązującą dwu- lub trójwymiarowe zadanie dyfuzji ze zmiennym współczynnikiem, z wykorzystaniem biblioteki Deal.II.
Zaimplementuj w Octave lub w C++ funkcję rozwiązującą anizotropowe zadanie dyfuzji w
z warunkiem brzegowym Dirichleta. Tutaj, dla zadanego
Treść automatycznie generowana z plików źródłowych LaTeXa za pomocą oprogramowania wykorzystującego LaTeXML.
strona główna | webmaster | o portalu | pomoc
© Wydział Matematyki, Informatyki i Mechaniki UW, 2009-2010. Niniejsze materiały są udostępnione bezpłatnie na licencji Creative Commons Uznanie autorstwa-Użycie niekomercyjne-Bez utworów zależnych 3.0 Polska.
Projekt współfinansowany przez Unię Europejską w ramach Europejskiego Funduszu Społecznego.
Projekt współfinansowany przez Ministerstwo Nauki i Szkolnictwa Wyższego i przez Uniwersytet Warszawski.