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 . W klasycznym modelu fizycznym dyfuzji, odpowiadałoby stężeniu jakiejś substancji, a współczynnik nazywalibyśmy współczynnikiem dyfuzji. Zadana funkcja 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 . 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 , ale tutaj dla uproszczenia (zadanie i tak będzie obliczeniowo wystarczająco skomplikowane) przyjmiemy, że
Współczynnik dyfuzji jest dodatnią funkcją skalarną, i ograniczoną: istnieją stałe absolutne takie, że .
jest kostką w , przy czym — 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.
Na początek przyjmijmy, że warunek brzegowy jest jednorodny: oraz . Wówczas zadanie (8.1) upraszcza się do równania Poissona:
(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 i metody różnic skończonych.
Gdy , to jest odcinkiem16Bez zmniejszenia ogólności założymy, że początek odcinka znajduje się w punkcie ., , a (8.3) staje się równaniem różniczkowym zwyczajnym z warunkiem brzegowym:
(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 ma odpowiadać wartości rozwiązania w węźle dyskretyzacji , , oraz , przy czym . 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 ,
gdzie
(8.9) |
Ponieważ interesują nas dobre przybliżenia (to znaczy przypadek, gdy jest małe — lub bardzo małe), znaczy to, że będzie (bardzo) duże. Nasza macierz jest macierzą rozrzedzoną, bo ma nieco mniej niż niezerowych elementów. Jej współczynnik wypełnienia (density), czyli stosunek liczby niezerowych do liczby wszystkich elementów macierzy, rzędu , maleje ze wzrostem . Nie ma więc sensu pamiętanie wszystkich 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
Nx
. Komenda repmat
klonuje daną macierz Nx
razy.
Dzięki reprezentacji naszej macierzy w formacie rzadkim, macierz rozmiaru zajmie nam w pamięci tylko około 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 bajtów w przypadku, gdybyśmy reprezentowali ją jako macierz pełną.
Spróbuj zaimplementować obliczanie , zastępując w kodzie powyżej
[-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 ) pozwoli nam wyznaczyć rozwiązanie układu kosztem 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;
Wyznacz przybliżone rozwiązanie równania
gdzie oraz
Dzieląc równanie przez stałą dostajemy (8.20) dla prawej strony równej . Ponieważ załamuje się w , weźmiemy siatkę, w której jeden z węzłów wypadnie akurat w . W przypadku siatki jednorodnej zagwarantujemy to sobie, biorąc nieparzyste . Wstępnie wydaje się, że biorąc , uzyskamy dostatecznie dużą rozdzielczość schematu (wszak wówczas ). 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 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 (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 , 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 . 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,
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 jest symetryczna? Czy jest trójdiagonalna tak, jak chcieliśmy?
Czy jest macierzą o diagonali równej 2?
Ile wynosi , gdy jest wektorem stałym? (Wynik powinien być zerowy z wyjątkiem pierwszego i ostatniego węzła.)
Czy jeśli , to rozwiązując zadanie dyskretne dostajemy w wyniku dyskretyzację funkcji ? A dla ?
Funkcja spełnia równanie Poissona dla . Należy zbadać, czy błąd rozwiązań dyskretnych uzyskiwanych dla siatek o oczku , , , …, 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.
Przeprowadź testy poprawności implementacji zarówno macierzy , jak i sposobu rozwiązywania jednowymiarowego równania Poissona.
Aby na przykład sprawdzić wynik działania operatora 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 .
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 , gdzie
przy czym
Wartości rozwiązania dyskretnego będą odpowiadać wartościom rozwiązania w węłach dyskretyzacji , tzn. .
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 . Otrzymamy więc w końcu równanie różnicowe postaci
(8.12) | ||||
(8.13) |
gdzie . Mamy więc do wyznaczenia niewiadomych, które w naturalny sposób układają się w macierz
(zwróćmy uwagę na to, że ,,fizyczne” współrzędne są odwrócone: wartości odpowiadające kolejnym współrzędnym w kierunku osi 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 . 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.:
(8.14) |
Analogicznie zdefiniowalibyśmy także wektor prawej strony . Będziemy więc szukać wektora , który spełnia liniowy układ równań o macierzowej postaci
gdzie jest pięciodiagonalną macierzą o blokowej strukturze,
(8.15) |
W kolejnych diagonalnych blokach znajdują się macierze trójdiagonalne , gdzie jest znaną już nam macierzą dyskretyzacji jednowymiarowego laplasjanu. Jest tych bloków . Używając symbolu Kroneckera dla iloczynu tensorowego macierzy mamy po prostu
Przypomnijmy na marginesie, że iloczyn tensorowy macierzy i jest macierzą o strukturze blokowej, postaci
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 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);
Zweryfikuj poprawność swojej implementacji macierzy .
W przypadku macierzy , prosta strategia polegająca na eliminacji Gaussa (z wykorzystaniem dodatkowo faktu, że jest pasmowa, o szerokości pasma ), dałaby nam szansę rozwiązać to równanie kosztem , co jest, przy dużych wielkościach i , 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 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.
Wyznacz przybliżone rozwiązanie równania dyfuzji w środowisku anizotropowym,
gdzie oraz
Narysuj wykres i zbadaj następnie, jak rozwiązanie zmienia się wraz ze zmianą .
Zgodnie z wyżej opisanym schematem, musimy wyznaczyć macierz odpowiadającą równaniu różnicowemu
(zwróćmy uwagę na obecność współczynnika przy dyskretyzacji drugiej pochodnej względem ). Zatem, postępując zgodnie z opisaną metodą, po dyskretyzacji równania dostaniemy macierz
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 — nie będzie klasyczne. Owszem, możemy spodziewać się rozwiązań słabych — klasy — 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 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 ? Przyczyną jest struktura, jaką mają macierze pX
, pY
, które Octave generuje poleceniem meshgrid
. Aby ułatwić wizualizację takich macierzy, indeksy odpowiadające zmiennej 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 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 różnicami skończonymi na regularnej siatce o oczku i wewnętrznych węzłach, prowadzi do zadania różnicowego
(8.16) |
dla , z odpowiednimi warunkami brzegowymi. Oczywiście . Mamy więc do wyznaczenia niewiadomych wewnątrz obszaru, które w naturalny sposób układają się w trójwymiarową tablicę
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 . 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:
Będziemy szukać wektora , który spełnia liniowy układ równań
gdzie jest siedmiodiagonalną macierzą o blokowej strukturze,
(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 dochodzimy do (!) Dodatkowo, struktura naszej (bardzo rozrzedzonej) macierzy jest na tyle skomplikowana dla bezpośredniego solvera opartego na eliminacji Gaussa, że szybko staje się on nieskuteczny.
Rozwiąż zagadnienie Poissona
gdzie przyjmuje wartość 1, zaburzoną o losowe wartości z przedziału . Wizualizuj rozwiązanie.
Tak sformułowane zadanie ma charakter czysto jakościowy, a nie ilościowy — rzeczywiście, skoro 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 ! 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 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.
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 , to już dla zadań rozmiaru 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.
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 na wartości i wektory własne,
skąd wynika, że
w przypadku dwuwymiarowym:
w przypadku trójwymiarowym:
Rozwiązanie układu z macierzą diagonalną kosztuje tylko operacji, natomiast mnożenia przez macierze 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.
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 (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 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.
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 , 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
nie wymaga wcale konstrukcji macierzy ; w zupełności wystarcza im funkcja, wyznaczająca dla zadanego wektora ilocznyn (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.
Gdy jest odcinkiem , (8.1) staje się równaniem różniczkowym zwyczajnym z warunkiem brzegowym:
(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 można wziąć na przykład
gdzie, zgodnie z intuicją, . Widzimy więc, że obliczenie działania dyskretnego operatora dyfuzji na wektorze (dane wzorem (8.20)), wymaga znajomości wartości także na brzegu obszaru i prowadzi do wyznaczenia wartości wyniku jedynie w węzłach wewnętrznych.
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 na brzegu przyjmuje wartości równe zero oraz spełnia
(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 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 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 ), 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ę , por. (8.22).
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 oraz .
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 , 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 ?
Następnie należy koniecznie przeprowadzić serię testów dla (pamiętając o tym by wybierane przez nas było ostro odgraniczone od zera) w sposób analogiczny jak to robiliśmy w przypadku dyskretnego operatora Laplace'a. Czy gdy , to zbieżność pogarsza się? (Powinna!)
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 . Pomyśl, jak to usprawnić.
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 niewiadomych, które w naturalny sposób układają się w macierz
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 dla oraz , iloraz różnicowy
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 . Wyznacz i zobrazuj wynik działania dyskretyzacji operatora dyfuzji
w przypadku, gdy na .
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 oraz , 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.
Zaimplementuj w Octave funkcję rozwiązującą trójwymiarowe zadanie dyfuzji.
Zaimplementuj w Octave funkcję rozwiązującą na kostce -wymiarowe () zadanie dyfuzji z warunkiem brzegowym Neumanna.
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 ze zmiennym współczynnikiem,
z warunkiem brzegowym Dirichleta. Tutaj, dla zadanego , macierz jest dodatnio określoną macierzą rozmiaru . Rozważ .
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.