Zagadnienia

8. Stacjonarne równanie dyfuzji

W bardzo wielu praktycznych modelach (biologicznych, chemicznych, fizycznych i nawet ekonomicznych) należy rozwiązać równanie różniczkowe postaci

-divaxux=fx,xΩ, (8.1)
ux=gxxΩ. (8.2)

Niewiadomą jest funkcja u:Ω¯R. W klasycznym modelu fizycznym dyfuzji, u odpowiadałoby stężeniu jakiejś substancji, a współczynnik a nazywalibyśmy współczynnikiem dyfuzji. Zadana funkcja f byłaby interpretowana jako zewnętrzne źródło substancji, a warunek brzegowy typu Dirichleta oznaczałby, że na brzegu obszaru utrzymujemy zadane stężenie równe g. Warto pamiętać, że równaniem tego samego lub podobnego typu można opisywać wiele innych sytuacji, na przykład rozchodzenie się ciepła w pewnym materiale.

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 ax, ale tutaj dla uproszczenia (zadanie i tak będzie obliczeniowo wystarczająco skomplikowane) przyjmiemy, że

  • Współczynnik dyfuzji jest dodatnią funkcją skalarną, a:Ω¯R+ i ograniczoną: istnieją stałe absolutne m,M takie, że 0<m|a()|M.

  • Ω jest kostką w Rd, przy czym d1,2,3 — czyli rozpatrujemy dyfuzję w co najwyżej trójwymiarowym obszarze o bardzo prostej geometrii. Co robić w przypadku, gdy Ω nie jest kostką, powiemy parę słów pod koniec rozdziału.

Zaczniemy więc od samodzielnej dyskretyzacji równania (8.1) w najprostszym przypadku.

8.1. Laplasjan: dyskretyzacja metodą różnic skończonych

Na początek przyjmijmy, że warunek brzegowy jest jednorodny: g=0 oraz ax1. Wówczas zadanie (8.1) upraszcza się do równania Poissona:

-Δux=fxxΩ, (8.3)
ux=0xΩ. (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 Ω i metody różnic skończonych.

8.1.1. Dyskretyzacja jednowymiarowego laplasjanu

Gdy d=1, to Ω jest odcinkiem16Bez zmniejszenia ogólności założymy, że początek odcinka znajduje się w punkcie x=0., Ω=0,X, a (8.3) staje się równaniem różniczkowym zwyczajnym z warunkiem brzegowym:

-d2dx2ux=fxx0,X, (8.5)
u0=uX=0. (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,

-ui-1-2ui+ui+1hx2=fii=1,,Nx, (8.7)
u0=uNx+1=0, (8.8)

gdzie ui ma odpowiadać wartości rozwiązania w węźle dyskretyzacji xi, uiuxi, oraz xi=ihx, przy czym hx=X/Nx+1. Odpowiedzią na pytanie, jak dobrze (i czy w ogóle) takie rozwiązanie dyskretne aproksymuje rozwiązanie dokładne, zajmujemy się na wykładzie z Numerycznych równań różniczkowych; tutaj interesuje nas przede wszystkim aspekt praktyczny, czyli metoda uzyskania konkretnego przybliżenia. Najpierw zobaczmy więc, do jakiego zagadnienia algebraicznego prowadzi równanie różnicowe (8.7).

Jest to oczywiście równanie liniowe na współczynniki UNx=u1,,uNxT,

TNxUNx=FNx,

gdzie

TNx=1hx22-1-12-1-12-1-12Nx×Nx. (8.9)

Ponieważ interesują nas dobre przybliżenia (to znaczy przypadek, gdy hx jest małe — lub bardzo małe), znaczy to, że Nx będzie (bardzo) duże. Nasza macierz jest macierzą rozrzedzoną, bo ma nieco mniej niż 3Nx niezerowych elementów. Jej współczynnik wypełnienia (density), czyli stosunek liczby niezerowych do liczby wszystkich elementów macierzy, rzędu 1/Nx, maleje ze wzrostem Nx. Nie ma więc sensu pamiętanie wszystkich Nx2 jej elementów — raczej, pamiętalibyśmy tylko jej niezerowe elementy. Do tego świetnie nadaje się format macierzy rozrzedzonych Octave. Dowolną macierz w takim formacie można utworzyć poleceniem 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 =X Nx =Nx. Komenda repmat klonuje daną macierz Nx razy.

Dzięki reprezentacji naszej macierzy w formacie rzadkim, macierz rozmiaru Nx zajmie nam w pamięci tylko około 83Nx=24Nx bajtów (w Octave wszystkie liczby rzeczywiste są domyślnie reprezentowane w podwójnej precyzji, a więc na 64 bitach, czyli 8 bajtach) — a nie standardowe 8Nx2 bajtów w przypadku, gdybyśmy reprezentowali ją jako macierz pełną.

Ćwiczenie 8.1

Spróbuj zaimplementować obliczanie Tx, zastępując w kodzie powyżej

[-1 2 -1]/(hx^2)
przez pozornie ,,równie dobrze wyglądające”
[-1 2 -1]/hx*hx
Czy dostajesz tę samą macierz Tx? To jeszcze jeden przyczynek do tego, ja ważne jest, by na różne sposoby zweryfikować poprawność konkretnej implementacji.

8.1.2. Rozwiązanie równania Poissona w obszarze jednowymiarowym

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 LDLT) pozwoli nam wyznaczyć rozwiązanie układu TNxUNx=FNx kosztem ONx flopów — a więc, z dokładnością do stałej, optymalnym. Standardowy operator rozwiązywania równań w Octave — ,,\” — 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;
Ćwiczenie 8.2

Wyznacz przybliżone rozwiązanie równania

-kd2dx2ux=fx,x0,2,
u0=u2=0,

gdzie k=2.45 oraz

fx=1/2-x,x0,1,1/x5,x1,2.
Rozwiązanie: 

Dzieląc równanie przez stałą k dostajemy (8.20) dla prawej strony równej fx/k. Ponieważ fx załamuje się w x=1, weźmiemy siatkę, w której jeden z węzłów wypadnie akurat w x=1. W przypadku siatki jednorodnej zagwarantujemy to sobie, biorąc nieparzyste Nx. Wstępnie wydaje się, że biorąc Nx=99X+1=199, uzyskamy dostatecznie dużą rozdzielczość schematu (wszak wówczas hx=10-2). Potem postaramy się sprawdzić inżynierską metodą, jaka jest jakość obliczonego rozwiązania.

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 TNx wykorzystaliśmy funkcję pomocniczą 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 Nx=399 (a więc, używając dwa razy drobniejszej siatki). Wtedy możemy ,,oszacować” popełniony błąd:

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 Oh2, por. wykład z numerycznych równań różniczkowych, rozdział dotyczący metody różnic skończonych dla równań eliptycznych) różni się w punktach starej siatki od oryginalnie uzyskanego jedynie o około 10-5. To oczywiście tylko jest pewna sugestia co do błędu realnego, gdyż na pewno możemy powiedzieć jedynie, że w dyskretnej normie maksimum określonej w węzłach rzadszej siatki,

u-UNxUNx-U2Nx+110-5 (?)+u-U2Nx+1wierzymy, że jest ,,małe” wobec poprzedniego.

8.1.2.1. Testy

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 TNx jest symetryczna? Czy jest trójdiagonalna tak, jak chcieliśmy?

  • Czy hx2TNx jest macierzą o diagonali równej 2?

  • Ile wynosi TNxUNx, gdy UNx jest wektorem stałym? (Wynik powinien być zerowy z wyjątkiem pierwszego i ostatniego węzła.)

  • Czy jeśli FNx=2, to rozwiązując zadanie dyskretne dostajemy w wyniku dyskretyzację funkcji xX-x? A dla FNx=0?

  • Funkcja ux=sin2πXx spełnia równanie Poissona dla fx=2πX2ux. Należy zbadać, czy błąd rozwiązań dyskretnych uzyskiwanych dla siatek o oczku hx, hx/2, hx/4, …, maleje zgodnie z teoretycznymi przewidywaniami dla tego zadania, por. wykład z numerycznych równań różniczkowych, rozdział o metodzie różnic skończonych dla równań eliptycznych.

Wykonanie tych testów pozostawiamy Czytelnikowi.

Ćwiczenie 8.3

Przeprowadź testy poprawności implementacji zarówno macierzy TNx, jak i sposobu rozwiązywania jednowymiarowego równania Poissona.

Wskazówka: 

Aby na przykład sprawdzić wynik działania operatora TNx na wektorze stałym, wydamy komendę

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 Nx.

8.1.3. Dyskretyzacja dwuwymiarowego laplasjanu

Chcąc rozwiązać dwuwymiarowe zadanie Poissona,

-2x2ux,y-2y2ux,y=fx,yx,y0,X×0,Y=Ω, (8.10)
ux,y=0x,yΩ, (8.11)

postąpimy analogicznie jak w poprzednim przypadku, korzystając z jednorodnej siatki węzłów na prostokącie Ω.

Jednorodna siatka na kwadracie, o identycznych kwadratowych oczkach.
Rys. 8.1. Regularna siatka na Ω.

Będziemy zatem mieli do czynienia z wartościami rozwiązania w punktach xi,yj, gdzie

xi=ihx,dla i=0,,Nx+1,orazyj=jhy,dla j=0,,Ny+1,

przy czym

hx=XNx+1orazhy=YNy+1.

Wartości rozwiązania dyskretnego ui,j będą odpowiadać wartościom rozwiązania w węłach dyskretyzacji xi,yj, tzn. ui,juxi,yj.

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:

2x2uxi,yjui-1,j-2ui,j+ui+1,jhx2

i analogicznie dla 2y2xi,yj. Otrzymamy więc w końcu równanie różnicowe postaci

-ui-1,j-2ui,j+ui+1,jhx2-ui,j-1-2ui,j+ui,j+1hy2=fi,ji=1,,Nx,j=1,,Ny, (8.12)
u0,j=uNx+1,j=ui,0=ui,Ny+1=0,i=0,,Nx+1,j=0,,Ny+1, (8.13)

gdzie fi,j=fxi,yj. Mamy więc do wyznaczenia N=NxNy niewiadomych, które w naturalny sposób układają się w macierz

u1,1u1,2u1,3u1,Nyu2,1u2,2u2,3u2,NyuNx,1uNx,2uNx,3uNx,Ny

(zwróćmy uwagę na to, że ,,fizyczne” współrzędne są odwrócone: wartości odpowiadające kolejnym współrzędnym w kierunku osi OX znajdują się w kolejnych wierszach jednej kolumny powyższej macierzy!). Podobnie jest z wartościami prawej strony równania. Aby jednak korzystać ze standardowych narzędzi Octave (na przykład operatora ,,\” 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 N. Jakkolwiek można to robić na wiele sposobów, nam będzie najwygodniej uporządkować elementy kolumnami17Ze względu na to, że łatwo to osiągnąć w Octave; patrz niżej.:

UN=u1,1,u2,1,u3,1,,uNx,1niewiadome pierwszej kolumny,u1,2,u2,2,u3,2,,uNx,2drugiej kolumny,,u1,Ny,u2,Ny,u3,Ny,,uNx,Nyostatniej kolumnyT. (8.14)

Analogicznie zdefiniowalibyśmy także wektor prawej strony FN. Będziemy więc szukać wektora UN, który spełnia liniowy układ równań o macierzowej postaci

PNUN=FN,

gdzie PN jest pięciodiagonalną macierzą o blokowej strukturze,

PN=TNx+2hy2I-1hy2I-1hy2ITNx+2hy2I-1hy2I-1hy2ITNx+2hy2I-1hy2I-1hy2ITNx+2hy2IN×N. (8.15)

W kolejnych diagonalnych blokach PN znajdują się macierze trójdiagonalne TNx+2hy2I, gdzie TNx jest znaną już nam macierzą dyskretyzacji jednowymiarowego laplasjanu. Jest tych bloków Ny. Używając symbolu Kroneckera dla iloczynu tensorowego macierzy mamy po prostu

PN=INyTNx+TNyINx.

Przypomnijmy na marginesie, że iloczyn tensorowy macierzy A i B jest macierzą o strukturze blokowej, postaci

a11Ba12Ba1MBa21Ba22Ba2MBaN1BaN2BaNMB.

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 PN będzie w Octave wyjątkowo krótka:

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);

Ćwiczenie 8.4

Zweryfikuj poprawność swojej implementacji macierzy PN.

8.1.4. Rozwiązanie równania Poissona w obszarze dwuwymiarowym

W przypadku macierzy PN, prosta strategia polegająca na eliminacji Gaussa (z wykorzystaniem dodatkowo faktu, że PN jest pasmowa, o szerokości pasma 2Nx), dałaby nam szansę rozwiązać to równanie kosztem ONx2Ny2, co jest, przy dużych wielkościach Nx i Ny, wartością trudną do zaakceptowania. Na szczęście, wbudowany Octave solver układów równań liniowych z macierzami rzadkimi, kryjący się za operatorem ,,\”, 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 FN w odpowiedni sposób przedstawimy w postaci długiego wektora 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.

Ćwiczenie 8.5

Wyznacz przybliżone rozwiązanie równania dyfuzji w środowisku anizotropowym,

2x2ux,y-k2y2ux,y=fx,y,x,y0,2×0,3=Ω,
ux,y=0,x,yΩ,

gdzie k=2.45 oraz

fx,y=1,x-112,y-212,0,w przeciwnym przypadku.

Narysuj wykres ux,y i zbadaj następnie, jak rozwiązanie zmienia się wraz ze zmianą k.

Rozwiązanie: 

Zgodnie z wyżej opisanym schematem, musimy wyznaczyć macierz PN odpowiadającą równaniu różnicowemu

-ui-1,j-2ui,j+ui+1,jhx2-kui,j-1-2ui,j+ui,j+1hy2=fi,j

(zwróćmy uwagę na obecność współczynnika k przy dyskretyzacji drugiej pochodnej względem y). Zatem, postępując zgodnie z opisaną metodą, po dyskretyzacji równania dostaniemy macierz

PN=INyTNx+kTNyINx.

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 C2Ω — nie będzie klasyczne. Owszem, możemy spodziewać się rozwiązań słabych — klasy H01Ω — ale czy uprawnione w tym przypadku jest używanie aproksymacji różnicowej dla drugiej pochodnej, opartej na rozwinięciach Taylora?

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 F=Fi.j określoną we wszystkich węzłach dyskretyzacji (łącznie z brzegowymi):

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 F? Przyczyną jest struktura, jaką mają macierze pX, pY, które Octave generuje poleceniem meshgrid. Aby ułatwić wizualizację takich macierzy, indeksy odpowiadające zmiennej x odpowiadają tam numerom kolumn — a nie, tak, jak my sobie to założyliśmy, wierszy. Określając więc wartości F na podstawie pX i pY, dostajemy F o zamienionych ze sobą wierszach i kolumnach. Dlatego na koniec musimy to odwrócić poleceniem permute19Tak, 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.

8.1.5. Dyskretyzacja i rozwiązanie równania Poissona w obszarze trójwymiarowym

Czytelnik zechce sam sprawdzić, że dyskretyzacja operatora Laplace'a w kostce Ω=0,X×0,Y×0,Z różnicami skończonymi na regularnej siatce o oczku hx,hy,hz i Nx×Ny×Nz wewnętrznych węzłach, prowadzi do zadania różnicowego

-ui-1,j,k-2ui,j,k+ui+1,j,khx2-ui,j-1,k-2ui,j,k+ui,j+1,khy2-ui,j,k-1-2ui,j,k+ui,j,k+1hz2=fi,j,k (8.16)

dla i=1,,Nx,j=1,,Ny,k=1,,Nz, z odpowiednimi warunkami brzegowymi. Oczywiście ui,j,kuihx,jhy,khz. Mamy więc do wyznaczenia N=NxNyNz niewiadomych wewnątrz obszaru, które w naturalny sposób układają się w trójwymiarową tablicę

ui,j,kNx×Ny×Nz.

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 N. Z objaśnionych wcześniej powodów, zrobimy to ,,kolumnami” — to znaczy tak, by w uzyskanym wektorze najszybciej zmieniała się pierwsza współrzędna, potem druga, a najwolniej — trzecia:

UN=(u1,1,u2,1,1,u3,1,1,,uNx,1,1niewiadome pierwszej kolumny i pierwszego plastra,u1,2,1,u2,2,1,u3,2,1,,uNx,2,1drugiej kolumny i pierwszego plastra,,
u1,Ny,1,u2,Ny,1,u3,Ny,1,,uNx,Ny,1ostatniej kolumny i pierwszego plastra,,
,
u1,Ny,Nz,u2,Ny,Nz,u3,Ny,Nz,,uNx,Ny,Nzostatniej kolumny i ostatniego plastra)T.

Będziemy szukać wektora UN, który spełnia liniowy układ równań

SNUN=FN,

gdzie SN jest siedmiodiagonalną macierzą o blokowej strukturze,

SN=INzINyTNx+INzTNyINx+TNzINyINx. (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 Nx=Ny=Nz=100 dochodzimy do N=106 (!) Dodatkowo, struktura naszej (bardzo rozrzedzonej) macierzy jest na tyle skomplikowana dla bezpośredniego solvera opartego na eliminacji Gaussa, że szybko staje się on nieskuteczny.

Ćwiczenie 8.6

Rozwiąż zagadnienie Poissona

-Δu=fΩ=(0,1)×(0,2)×(0,3),
u=0na Ω,

gdzie f przyjmuje wartość 1, zaburzoną o losowe wartości z przedziału -0.1,0.1. Wizualizuj rozwiązanie.

Rozwiązanie: 

Tak sformułowane zadanie ma charakter czysto jakościowy, a nie ilościowy — rzeczywiście, skoro f jest poddana losowemu zaburzeniu, to trudno spodziewać się, by błąd popełniany z powodu dyskretyzacji miał inny niż losowy charakter i dlatego jakikolwiek konkretny wynik sam w sobie jest mało użyteczny: wszak rodzielczość użytej przez nas siatki nigdy nie będzie wystarczająca, by uchwycić wszelkie wahnięcia f! Jednak zadanie będzie dla nas dobrym pretekstem do tego, by

  • 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 80×80×80 kończy się20Na komputerze z 2GB pamięci RAM. Na szybkiej maszynie z ,,nieograniczonymi” zasobami (wieloprocesorowy Intel Xeon 2.4GHz, 64GB RAM), rozwiązanie udaje się policzyć, owszem, wykorzystując w tym celu (w MATLABie) ponad 8.5 GB pamięci i 400 sekund czasu rzeczywistego. komunikatem o błędzie z powodu braku wystarczających zasobów.

8.1.5.1. Krytyka standardowego solvera bezpośredniego

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 500×500, to już dla zadań rozmiaru 1000×1000 staje się mało efektywny, a jego wymagania pamięciowe stają się duże. Sytuacja staje się jeszcze bardziej dramatyczna, gdy przejdziemy do zadań trójwymiarowych, co mieliśmy okazję oglądać w ostatnim przykładzie. Jasne jest, że stosowane przez bezpośredni solver algorytmy reorderingu niewiadomych i równań są nieskuteczne w przypadku dyskretyzacji trójwymiarowych — nawet tak regularnych jak nasza.

8.1.5.2. Solver oparty na FFT

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 TN na wartości i wektory własne,

TN=QNΛNQNT,

skąd wynika, że

  • w przypadku dwuwymiarowym:

    PN=QNyQNxINyΛNx+ΛNyINxmacierz diagonalnaQNxTQNyT.
  • w przypadku trójwymiarowym:

    SN=QNzQNyQNxINzINyΛNx+INzΛNyINx+ΛNzINyINxmacierz diagonalnaQNxTQNyTQNzT.

Rozwiązanie układu z macierzą diagonalną kosztuje tylko N operacji, natomiast mnożenia przez macierze QN można łatwo zrealizować (niskim kosztem) za pomocą algorytmu FFT, dostępnego w Octave. Więcej informacji na ten temat (w tym — informację o jeszcze szybszym algorytmie!) można znaleźć w wykładach z Matematyki Obliczeniowej II.

Przykład 8.1 (Trójwymiarowe równanie Poissona przez FFT)

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 100×100×100 (to milion niewiadomych!) w czasie poniżej 1 sekundy (sic!) na komputerze Core 2 Duo 2.4 GHz z pamięcią 2GB. Nawet zadanie postawione na siatce 200×200×200 wciąż liczy się w więcej niż przyzwoitym czasie: poniżej 7 sekund!

Kluczowa dla szybkości działania solvera jest funkcja fftsolve3dd, wykorzystująca umiejętność Octave przykładania FFT ,,wzdłuż” zadanego wymiaru macierzy.

Ćwiczenie 8.7

Zmodyfikuj powyższy kod oraz funkcję fftsolve3dd tak, by mogła działać w przypadku dyskretyzacji dwuwymiarowego równania Poissona.

Quiz 8.1
  1. Czy warto zastosować powyższy algorytm do rozwiązywania jednowymiarowego zadania Poissona?


    Dobrze Metoda oparta na eliminacji Gaussa, wykorzystująca strukturę macierzy trójdiagonalnej, jest w tym wypadku optymalna

    Źle Pomyśl, ile kosztuje rozwiązanie układu równań z macierzą trójdiagonalną

  2. Czy w przypadku dwuwymiarowym opisana powyżej metoda oparta na FFT będzie dawała rozwiązanie obarczone większym błędem niż w sytuacji gdy zastosujemy w Octave standardowy operator rozwiązywania równań liniowych?


    Dobrze Odpowiedź, przynamniej teoretycznie, brzmi: nie. Obie metody rozwiązują ten sam układ równań i obie są metodami bezpośrednimi: w arytmetyce dokładnej dają dokładne rozwiązanie. Ponieważ w praktyce obliczenia wykonujemy w arytmetyce podwójnej precyzji, a każda z metod wykonuje inne rachunki, należy spodziewać się niewielkiej różnicy, ,,na poziomie precyzji arytmetyki”. Większy wpływ na wynik może mieć złe uwarunkowanie samej macierzy, a nie różnice między solverami. Patrz także [11]

    Źle To jeszcze nie uzasadnienie, ale… która z metod wyznaczy rozwiązanie niższym kosztem. Zajrzyj do [11]

Ćwiczenie 8.8

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.

Wskazówka: 

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?

8.2. Równanie dyfuzji

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 Ω, ale jednocześnie współczynnik dyfuzji nie jest stały w całym obszarze: aby go zastosować, musieliśmy założyć, że mamy do czynienia z równaniem Poissona, bo tylko dla niego znaliśmy wartości i wektory własne odpowiednich macierzy.

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

Ax=b

nie wymaga wcale konstrukcji macierzy A; w zupełności wystarcza im funkcja, wyznaczająca dla zadanego wektora v ilocznyn Av (takie metody iteracyjne nazywamy metodami operatorowymi). W naszym przypadku będzie to wielkim ułatwieniem, gdyż obliczenie działania dyskretnego operatora dyfuzji na zadanej funkcji siatkowej będzie łatwe do osiągnięcia — w przeciwieństwie do jawnej konstrukcji macierzy takiego operatora. To jeszcze jeden powód, by zastosować metodę PCG.

Aby przetestować nasze nowe podejście, zaczniemy od przypadku jednowymiarowego, uwzględniając (wreszcie!) przypadek, gdy warunek brzegowy nie jest jednorodny.

8.2.1. Dyskretyzacja zadania jednowymiarowego

Gdy Ω jest odcinkiem 0,X, (8.1) staje się równaniem różniczkowym zwyczajnym z warunkiem brzegowym:

-ddxaxddxux=fxx0,X, (8.18)
u0=g0,uX=gX. (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

-ai-1ui-1-2aiui+ai+1ui+1hx2=fi

dość kiepsko aproksymuje nasze równanie, a znacznie lepsze własności (wiedza nie szkodzi!) ma aproksymacja postaci

-ai-1/2ui-1-ai-1/2+ai+1/2ui+ai+1/2ui+1hx2=fii=1,,Nx, (8.20)
u0=g0,uNx+1=gNx+1, (8.21)

gdzie za ai+1/2 można wziąć na przykład

ai+1/2=ai+ai+12lubai+1/2=axi+1/2.

gdzie, zgodnie z intuicją, xi+1/2=xi+xi+12. Widzimy więc, że obliczenie działania dyskretnego operatora dyfuzji DNx:RNx+2RNx na wektorze UNx (dane wzorem (8.20)), wymaga znajomości wartości także UNx na brzegu obszaru i prowadzi do wyznaczenia wartości wyniku jedynie w węzłach wewnętrznych.

8.2.1.1. Uwzględnienie warunków brzegowych

W naszym zadaniu poszukujemy rozwiązania postaci

UNx=g0,u1,,uNxrealne niewiadome,gNx+1T

takiego, że

DNxUNx=f1,,fNxT.

Powyższe zadanie, z niejednorodnym warunkiem brzegowym, bardzo łatwo sprowadzić do zadania z warunkiem jednorodnym. Rzeczywiście, oznaczając

U~Nx=UNx-g0,0,,0wewnętrzne,gN+1T

mamy, że U~Nx na brzegu przyjmuje wartości równe zero oraz spełnia

DNxU~Nx=f1,,fNxT-DNxg0,0,,0,gN+1T. (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 DNx w Octave:

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 DNx na

  • 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 Nx+2), użylibyśmy wywołania

    F = multdiff1dd(zeros(Nx,1), hx, Ax, G);
    

Ostatnie wywołanie pasuje jak ulał do wyznaczenia poprawki na prawą stronę DNxg0,0,,0,gN+1T, por. (8.22).

Ćwiczenie 8.9

Pamiętając o konieczności nieustannego weryfikowania poprawności zaimplementowanych funkcji, przygotuj i przeprowadź serię testów sprawdzających funkcję multdiff1dd.

Wskazówka: 

Warto wykorzystać w tym celu m.in. napisane wcześniej funkcje testujące/rozwiązujące równanie Poissona z jednorodnym warunkiem brzegowym.

8.2.2. Rozwiązanie równania dyfuzji w obszarze jednowymiarowym

Zgodnie z wcześniej przyjętą strategią, zastosujemy iteracyjną metodę rozwiązywania równania. Dla ustalenia uwagi przyjmieny, że ax=x+1 oraz fx=gx=1.

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;
Ćwiczenie 8.10

Czy w przypadku jednowymiarowej dyfuzji nie można byłoby w prosty sposób użyć solvera bezpośredniego?

Rozwiązanie: 

Faktycznie, mamy

DNx=1hx2-a1/2a1/2+a3/2-a3/2-a3/2a3/2+a5/2-a5/2-aNx-2/2aNx-2/2+aNx-1/2-aNx-1/2-aNx-1/2aNx-1/2+aNx+1/2-aNx+1/2Nx×Nx+2.

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…

Ćwiczenie 8.11

Pamiętając o konieczności nieustannego weryfikowania poprawności zaimplementowanych funkcji, przygotuj i przeprowadź serię testów sprawdzających opracowaną metodę.

Wskazówka: 

Przede wszystkim, warto sprawdzić, co dzieje się w przypadku ax1, czyli w znanym nam doskonale przypadku równania Poissona. Czy dla testowanych prawych stron dostajemy zawsze niemal identyczne rozwiązania? (doskonałą prawą stroną jest wektor losowy!) Czy zawsze dostajemy zbieżność PCG w jednej iteracji? Co dzieje się, gdy zwiększamy Nx?

Następnie należy koniecznie przeprowadzić serię testów dla ax1 (pamiętając o tym by wybierane przez nas a było ostro odgraniczone od zera) w sposób analogiczny jak to robiliśmy w przypadku dyskretnego operatora Laplace'a. Czy gdy maxaxminax1, to zbieżność pogarsza się? (Powinna!)

Ćwiczenie 8.12

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 Tx. Pomyśl, jak to usprawnić.

Rozwiązanie: 

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);

8.2.3. Dyskretyzacja dwuwymiarowego operatora dyfuzji

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ć:

-ai-1/2,jui-1,j-ai-1/2,j+ai+1/2,jui,j+ai+1/2,jui+1,jhx2
-ai,j-1/2ui,j-1-ai,j-1/2+ai,j+1/2ui,j+ai,j+1/2ui,j+1hy2
=fi,ji=1,,Nx,j=1,,Ny,

z warunkiem brzegowym

u0,j=g0,j,uNx+1,j=gNx+1,j,ui,0=gi,0,ui,Ny+1=gi,Ny+1,i=1,,Nx,j=1,,Ny.

Mamy do wyznaczenia N=NxNy niewiadomych, które w naturalny sposób układają się w macierz

UN=u1,1u1,2u1,3u1,Nyu2,1u2,2u2,3u2,NyuNx,1uNx,2uNx,3uNx,Ny.

Zauważmy, że aby na przykład wyznaczyć iloraz różnicowy

F=-ui-1,j+2ui,j-ui+1,jhx2,

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 ai+1/2,j dla i=0,,Nx oraz j=1,,Ny, iloraz różnicowy

F=-ai-1/2,jui-1,j+ai-1/2,j+ai+1/2,jui,j-ai+1/2,jui+1,jhx2

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.

Ćwiczenie 8.13

Zaimplementuj funkcję F = multdiff2dd(Uin, h, Ax, Ay, U), będącą analogonem opracowanej wcześniej funkcji jednowymiarowej. Niech ax,y=xy+1. Wyznacz i zobrazuj wynik działania dyskretyzacji operatora dyfuzji -divax,yux,y w przypadku, gdy ux,y=xy na Ω¯=0,2×0,3.

Rozwiązanie: 

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);
end
Podczas 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);

8.2.4. Rozwiązanie równania dyfuzji w obszarze dwuwymiarowym

Sama procedura rozwiązania równania nie zmieni się zanadto w stosunku do przypadku jednowymiarowego; dla ustalenia uwagi przyjmiemy, że ax,y=xy+1 oraz fx,y=gx,y=1, a wszystkie pozostałe parametry zadania są takie same, jak w uprzednio rozważanym zagadnieniu Poissona w prostokącie.

%
% 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.

Ćwiczenie 8.14 (trudne)

Zaimplementuj w Octave funkcję rozwiązującą trójwymiarowe zadanie dyfuzji.

Ćwiczenie 8.15 (trudne)

Zaimplementuj w Octave funkcję rozwiązującą na kostce d-wymiarowe (d1,2,3) zadanie dyfuzji z warunkiem brzegowym Neumanna.

8.3. Uwagi i uzupełnienia

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.

Ćwiczenie 8.16 (trudne)

Zaimplementuj w C/C++ funkcję rozwiązującą dwu- lub trójwymiarowe zadanie dyfuzji ze zmiennym współczynnikiem, z wykorzystaniem biblioteki Deal.II.

Ćwiczenie 8.17 (trudne)

Zaimplementuj w Octave lub w C++ funkcję rozwiązującą anizotropowe zadanie dyfuzji w Ω=0,1d ze zmiennym współczynnikiem,

-divAxux=fx,xΩ,

z warunkiem brzegowym Dirichleta. Tutaj, dla zadanego xΩ, macierz Ax jest dodatnio określoną macierzą rozmiaru d×d. Rozważ d1,2,3.

Treść automatycznie generowana z plików źródłowych LaTeXa za pomocą oprogramowania wykorzystującego LaTeXML.

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.