MN05LAB: Różnice pomiędzy wersjami

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania
Przykry (dyskusja | edycje)
mNie podano opisu zmian
Przykry (dyskusja | edycje)
mNie podano opisu zmian
Linia 4: Linia 4:
-->
-->
   
   
=Ćwiczenia: normy i uwarunkowanie=
=Ćwiczenia: rozwiązywanie układów równań liniowych=


<div style="margin-top:1em; padding-top,padding-bottom:1em;">
<div style="margin-top:1em; padding-top,padding-bottom:1em;">
<span  style="display: block; background-color:#fefeee; border-bottom: 1px solid #E5E5E5; line-height: 1.1em; padding-bottom: 0.2em; font-variant:small-caps; color:#1A6ABF;">Ćwiczenie: Normy macierzowe</span>  
<span  style="display: block; background-color:#fefeee; border-bottom: 1px solid #E5E5E5; line-height: 1.1em; padding-bottom: 0.2em; font-variant:small-caps; color:#1A6ABF;">Ćwiczenie: Mnożyć przez odwrotność to nie zawsze to samo...</span>  
<div class="exercise">
<div class="exercise">


Pokazać, że dla macierzy
W Octave układ równań <math>\displaystyle Ax=b</math> rozwiązujemy korzystając z "operatora
<math>\displaystyle A=(a_{i,j})_{i.j=1}^n\inR^{n\times n}</math> mamy
rozwiązywania równania"
<div class="code" style="background-color:#e8e8e8; padding:1em"><pre>
x = A \ b;
</pre></div>
W tym celu Octave oczywiście wewenętrznie wykorzystuje funkcję LAPACKa
<code>DGESV</code>. Ale w Octave jest także funkcja <code>inv</code>, wyznaczająca macierz
odwrotną, więc niektóre (nie najlepsze, oględnie mówiąc) podręczniki zalecają
<div class="code" style="background-color:#e8e8e8; padding:1em"><pre>
x = inv(A)*b;
</pre></div>
Przedyskutuj, które podejście jest lepsze i dlaczego. Przeprowadź eksperymenty
numeryczne weryfikujące Twoją tezę.
</div></div>


<center><math>\displaystyle \|A\|_1 \,=\, \|A^T\|_\infty \,=\,
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie </span><div class="mw-collapsible-content" style="display:none"><div style="margin-left:1em">
        \max_{1\le j\le n}\sum_{i=1}^n |a_{i,j}|
Wyznaczenie <math>\displaystyle A^{-1}</math> praktycznie nigdy nie jest konieczne w obliczeniach, a
</math></center>
zwłaszcza do rozwiązania układu równań. Nie dość, że jest bardziej kosztowne, to
dodatkowo nie jest numerycznie poprawne.


</div></div>
Przykładowy test mógłby wyglądać w poniższy sposób. Duże macierze losowe są
zazwyczaj dobrze uwarunkowane i najczęściej nieosobliwe.


oraz
<div class="code" style="background-color:#e8e8e8; padding:1em"><pre>
N = 400; A = rand(N,N);x = rand(N,1); b = A*x;
tic; X = A \ b; tX = toc;
tic; Y = inv(A)*b; tY = toc;
[norm(X-x)/norm(x),  norm(Y-x)/norm(x); tX, tY]
</pre></div>
<div class="output" style="background-color:#e0e8e8; padding:1em"><pre>
ans =
  1.9861e-13  2.8687e-13
  2.3644e-01  4.0609e-01
</pre></div>
Wyszło (z grubsza) dwa razy szybciej i dwa razy dokładniej.


<center><math>\displaystyle \|A\|_\infty \,=\, \max_{1\le i\le n}\sum_{j=1}^n |a_{i,j}|.
</div></div></div>
</math></center>


<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie </span><div class="mw-collapsible-content" style="display:none"><div style="margin-left:1em">
<div style="margin-top:1em; padding-top,padding-bottom:1em;">
Pokażemy pierwszą równość, druga dowodzi się analogicznie.
<span style="display: block; background-color:#fefeee; border-bottom: 1px solid #E5E5E5; line-height: 1.1em; padding-bottom: 0.2em; font-variant:small-caps; color:#1A6ABF;">Ćwiczenie: Wybór elementu głównego jest naprawdę ważny!</span>  
<div class="exercise">


Najpierw udowodnimy, że dla dowolnego wektora <math>\displaystyle x</math> o normie <math>\displaystyle ||x||_1 = 1</math>,
Rozwiąż prosty układ równań
<center><math>\displaystyle ||Ax||_1 \leq \max_{1\le j\le n}\sum_{i=1}^n
|a_{i,j}|, </math></center>


a następnie wskażemy taki wektor <math>\displaystyle x</math>, dla którego zachodzi równość.
<center><math>\displaystyle \aligned 10^{-18} x_1 + x_2 &= 1,\\
x_1 + 2x_2 &= 4,
\endaligned</math></center>


Istotnie, dla <math>\displaystyle ||x||_1 = 1</math>,
czterema sposobami:
<center><math>\displaystyle
* na kartce papieru (dokładnie!)
|(Ax)_i| = |\sum_{j=1}^n a_{ij}x_j| \leq \sum_{j=1}^n |a_{ij}|\cdot|x_j| \leq
* w komputerze, w arytmetyce podwójnej precyzji, korzystając z rozkładu LU <strong>bez wyboru elementu głównego</strong>
\max_j|a_{ij}|\sum_{j=1}^n |x_j| = \max_j|a_{ij}|,
* jak poprzednio, ale <strong>z wyborem elementu głównego w kolumnie</strong>
</math></center>
* w LAPACKu lub Octave.
 
bo <math>\displaystyle \sum_{j=1}^n |x_j|  = ||x||_1 = 1</math>, zatem
Porównaj uzyskane rozwiązania i wyciągnij wnioski.
<center><math>\displaystyle
</div></div>
||Ax||_1 = \sum_i |(Ax)_i|\leq \max_j \sum_{i=1}^n |a_{ij}|.
</math></center>
 
Niech teraz <math>\displaystyle i_0</math> będzie indeksem kolumny macierzy <math>\displaystyle A</math>, dla którego powyższe
maksimum jest przyjmowane. Wtedy w powyższym oszacowaniu równość jest
przyjmowana dla wektora <math>\displaystyle x=e_{i_0}</math>, czyli wektora, który na współrzędnej <math>\displaystyle i_0</math>
ma jedynkę, a poza nią --- same zera.


<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie </span><div class="mw-collapsible-content" style="display:none"><div style="margin-left:1em"> 
Rozwiązanie uzyskane bez wyboru elementu głównego jest bardzo mało precyzyjne <math>\displaystyle x_1</math> jest obarczone bardzo duzym błędem! Prosta analiza poazuje,  że winę za to ponosi bardzo wielki współczynnik w macierzy <math>\displaystyle L</math>. Tak nie jest, gdy stosujemy wybór elementu: elementy <math>\displaystyle L</math> są zawsze mniejsze od 1. Dlatego inne rozwiązania numeryczne dają w przypadku naszej macierzy wynik, będący bardzo dobrym przybliżeniem wyniku dokładnego. ''A propos:'' Octave korzysta z <code>DGESV</code> LAPACKa, a <code>DGESV</code> korzysta z wyboru w kolumnie...
</div></div></div>
</div></div></div>


Linia 55: Linia 83:
<div class="exercise">
<div class="exercise">


Pokaż, że dla macierzy rzeczywistej <math>\displaystyle N\times M</math>,
Zapisz w Octave algorytm rozkładu LU macierzy (bez wyboru elementu głównego) działający ''in situ''.  
<center><math>\displaystyle
||A||_2 = \max \{\sqrt{\lambda} : \lambda  \mbox{ jest wartością własną macierzy }
A^TA\}.
</math></center>


<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Wskazówka </span><div class="mw-collapsible-content" style="display:none">
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Wskazówka </span><div class="mw-collapsible-content" style="display:none">
<div style="font-size:smaller; background-color:#efe"> Macierz <math>\displaystyle A^TA</math> jest macierzą symetryczną, co można powiedzieć o jej
<div style="font-size:smaller; background-color:#efe"> Pamiętaj, pętle w Octave wykonują się bardzo wolno! </div>
wartościach i wektorach własnych? </div>
</div></div>
</div></div>


Wykorzystaj go do napisania funkcji, która rozwiąże układ równań <math>\displaystyle Ax=b</math>.
Przetestuj tę funkcję na kilku macierzach i porównaj czas jego działania z czasem
wykonania operacji <code>x = A\b</code>.
Spróbuj zastosować swój algorytm do kilku specjalnych macierzy:
* Hilberta dużego wymiaru
* diagonalnej z jednym elementem bardzo małym (a nawet równym zero)
</div></div>
</div></div>


<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie </span><div class="mw-collapsible-content" style="display:none"><div style="margin-left:1em">   
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie </span><div class="mw-collapsible-content" style="display:none"><div style="margin-left:1em">   
Niech <math>\displaystyle B= A^TA</math>. Jako macierz symetryczna, ma ona rozkład
Najprostszy wariant to po prostu przepisanie algorytmu z wykładu, który już
<center><math>\displaystyle  
nawet trochę wykorzystywał notację dwukropkową MATLABa:
B = Q^T\Lambda Q,
<div class="code" style="background-color:#e8e8e8; padding:1em"><pre>
</math></center>
function [L,U] = lufa(A)
# Na początku warto dodać trochę sprawdzania danych: czy A jest kwadratowa? a w ogóle,
# czy jest macierzą? te linie pozostawiamy inwencji Czytelnika
 
N = size(A,1);
for k=1:N-1
if (A(k,k) == 0.0)
return;
end
for i=k+1:N #wyznaczenie <math>\displaystyle k</math>-tej kolumny <math>\displaystyle L</math>
A(i,k) = A(i,k)/A(k,k);
end
for j=k+1:N #modyfikacja podmacierzy <math>\displaystyle A_{22} = A_{22} - l_{21}u_{12}^T</math>
for i=k+1:N
A(i,j) -= A(i,k)*A(k,j);
end
end
end
L = tril(A,-1) + eye(N);
U = triu(A);
end
</pre></div>
Jednak w tej implementacji mamy do czynienia z nawet potójnie zagnieżdżonymi
pętlami, co dla nawet średnich <math>\displaystyle N</math> może być powodem sporego spowolnienia
algorytmu. Warto więc uniknąć pętli, odwołując się do instrukcji wektorowych i
blokowych.
 
<div class="code" style="background-color:#e8e8e8; padding:1em"><pre>
function [L,U] = lufb(A)
# Na początku warto dodać trochę sprawdzania danych: czy A jest kwadratowa? a w ogóle,
# czy jest macierzą? te linie pozostawiamy inwencji Czytelnika
 
N = size(A,1);
for k=1:N-1
if (A(k,k) == 0.0)
return;
end
#wyznaczenie <math>\displaystyle k</math>-tej kolumny <math>\displaystyle L</math>
A(k+1:N,k) = A(k+1:N,k)/A(k,k);
#modyfikacja podmacierzy <math>\displaystyle A_{22} = A_{22} - l_{21}u_{12}^T</math>
A(k+1:N,k+1:N) -= A(k+1:N,k)*A(k,k+1:N);
end
L = tril(A,-1) + eye(N);
U = triu(A);
end
</pre></div>
<div class="code" style="background-color:#e8e8e8; padding:1em"><pre>
[Kod testujący lufa(0 i lufb()]
N = 100;
A = rand(N) + 1000*eye(N);
 
format short e;
 
t = ta = tb = Inf;
 
K = 5;
 
for i = 0:K
tic; [L, U] = lu(A); t = min(t,toc);
tic; [La, Ua] = lufa(A); ta = min(ta,toc);
tic; [Lb, Ub] = lufb(A); tb = min(tb,toc);
end
 
disp("Czasy: lu(), lufa(), lufb()"); [t, ta, tb]
 
disp("Błąd:");
[norm(L*U - A,inf), norm(La*Ua - A,inf), norm(Lb*Ub - A,inf)]
</pre></div>
Dla macierzy wymiaru <math>\displaystyle N=100</math> dostaliśmy na Pentium 1.5GHz
 
{| border=1
|+ <span style="font-variant:small-caps"> </span>
|-
| Implementacja  ||  lu (oryginalne)  ||  lufa  ||  lufb
|-
| Czas (s)  ||  4.0436e-03  ||  1.5877e+01  ||    1.0767e-01
|-
| Błąd <math>\displaystyle ||LU-A||_\infty</math> ||  3.4596e-13  ||  8.0747e-13  ||  8.0747e-13
 
|}


gdzie <math>\displaystyle Q</math> jest macierzą ortogonalną, <math>\displaystyle Q^TQ=I</math>, natomiast <math>\displaystyle \Lambda</math> jest macierzą
Zwróć uwagę na charakterystyczne fakty:
diagonalną (z wartościami własnymi <math>\displaystyle B</math> na diagonali). Poza tym <math>\displaystyle B</math> jest
* Usunięcie wewnętrznych pętli stukrotnie(!) przyspieszyło <code>lufb</code> w
nieujemnie
stosunku do <code>lufa</code>
określona (dlaczego?), więc wartości własne <math>\displaystyle B</math> są nieujemne, zatem wyciąganie z
* Gotowa funkcja Octave wciąż jest stukrotnie(!) szybsza od mojej
nich pierwiatka ma sens!
najszybszej funkcji.
To jest typowe, gdy korzystamy ze środowisk typu Octave czy MATLAB. Czasem
nawet lepiej napisać implementację wykonującą więcej obliczeń, ale korzystającą
w większm stopniu z wbudowanych funkcji Octave, niż napisać samemu implementację
liczącą mniej, ale korzystającą z licznych instrukcji <code>for...</code> i podobnych.


Dla dowolnego wektora <math>\displaystyle x</math> mamy
Innym sposobem przyspieszenia działania funkcji Octave jest ich prekompilacja.
<center><math>\displaystyle
To w znacznym stopniu usuwa problemy związanie z korzystaniem z pętli, ale
||Ax||_2^2  = (Ax)^TAx = x^T A^TA x = x^T B x = x^TQ^T\Lambda Q x,
wykracza poza zakres naszego kursu.
</math></center>


skąd, definiując <math>\displaystyle y=Qx</math>,
Funkcja wykorzystująca gotowy rozkład LU do rozwiązania układu <math>\displaystyle Ax=b</math> w MATLABie miałaby postać
<center><math>\displaystyle
||A||_2^2 = \max_x\frac{||Ax||_2^2 }{||x||_2^2} = \max_y\frac{||\Lambda y||_2^2
}{||y||_2^2} = \max \{\lambda_i\},
</math></center>


bo <math>\displaystyle ||Qx||_2 = ||y||_2</math>.  
<div class="code" style="background-color:#e8e8e8; padding:1em"><pre>
function x = sol(L,U,b)
x = U \ ( L \ b);
end
</pre></div>
gdyż MATLAB prawidłowo rozpoznaje układy z macierzą trójkątną i stosuje szybszy
algorytm. Niestety, do tej pory w Octave nie jest to możliwe; nie ma też
funkcji, która rozwiązywałaby układ z macierzą trójkątną (wykorzystując np.
LAPACKa). Zrób więc to i... wyślij do twórców Octave'a!
</div></div></div>
</div></div></div>


<div style="margin-top:1em; padding-top,padding-bottom:1em;">
<div style="margin-top:1em; padding-top,padding-bottom:1em;">
<span  style="display: block; background-color:#fefeee; border-bottom: 1px solid #E5E5E5; line-height: 1.1em; padding-bottom: 0.2em; font-variant:small-caps; color:#1A6ABF;">Ćwiczenie</span>  
<span  style="display: block; background-color:#fefeee; border-bottom: 1px solid #E5E5E5; line-height: 1.1em; padding-bottom: 0.2em; font-variant:small-caps; color:#1A6ABF;">Ćwiczenie: Czy Twoje programy działają <strong>naprawdę</strong> szybko?</span>  
<div class="exercise">
<div class="exercise">


Dla wektora <math>\displaystyle  x=(x_j)_{j=1}^n\inR^n</math>, niech
Zrób zadanie poprzednie programując, niczym Zosia-Samosia, wszystko od początku do końca w czystym C. Porównaj czasy działania Twojego programu i programu wywołującego po prostu procedurę biblioteczną LAPACKa
<math>\displaystyle rd_\nu( x)=(rd_\nu(x_j))_{j=1}^n</math>. Pokazać, że
<code>DGESV</code>, najlepiej wspartą dobrze podrasowanymi BLASami.


<center><math>\displaystyle \| x-rd_\nu( x)\|_p\,\le\,\nu\,\| x\|_p
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Wskazówka </span><div class="mw-collapsible-content" style="display:none">
</math></center>
<div style="font-size:smaller; background-color:#efe"> To zadanie to wyzwanie! </div>
</div></div>


dla <math>\displaystyle 1\le p\le\infty</math>.
</div></div>
</div></div>
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie </span><div class="mw-collapsible-content" style="display:none"><div style="margin-left:1em"> 
No tak, namawiamy Cię do zmierzenia się z LAPACKiem i ATLASem....
Prościutki program --- po prostu tłumaczącu algorytm z wykładu bezpośrednio
na C --- u nas spisał
się, dla macierzy wymiaru <math>\displaystyle N=1\ldots 1024</math>, następująco:
[[Image:MNdegsvtiming.png|thumb|450px|center|Prosty solver w C kontra LAPACK i LAPACK z ATLASem.
Zwróć uwagę na piki zwykłego kodu dla <math>\displaystyle N</math> będących potęgami dwójki]]
[[Image:MNdegsvtiminglogscale.png|thumb|450px|center|Skala logarytmiczna pozwala lepiej ocenić
różnice pomiędzy czasami wykonania. Prosty program w C jest około dziesięć razy
wolniejszy od kodu korzystającego z LAPACKa i około 50 razy wolniejszy od kodu
korzystającego z LAPACKa i ATLASa.]]
</div></div></div>


<div style="margin-top:1em; padding-top,padding-bottom:1em;">
<div style="margin-top:1em; padding-top,padding-bottom:1em;">
<span  style="display: block; background-color:#fefeee; border-bottom: 1px solid #E5E5E5; line-height: 1.1em; padding-bottom: 0.2em; font-variant:small-caps; color:#1A6ABF;">Ćwiczenie</span>  
<span  style="display: block; background-color:#fefeee; border-bottom: 1px solid #E5E5E5; line-height: 1.1em; padding-bottom: 0.2em; font-variant:small-caps; color:#1A6ABF;">Ćwiczenie: Układy równań z wieloma prawymi stronami</span>  
<div class="exercise">
<div class="exercise">


Dla macierzy <math>\displaystyle A=(a_{i,j})_{i,j=1}^n</math>, niech
Podaj sposób taniego wyznaczenia rozwiązania sekwencji <math>\displaystyle k<N</math> układów równań z tą
<math>\displaystyle rd_\nu(A)=(rd_\nu(a_{i,j}))_{i,j=1}^n</math>. Pokazać, że
samą macierzą <math>\displaystyle A\in R^{N\times N}</math> i różnymi prawymi stronami:
 
<center><math>\displaystyle  
<center><math>\displaystyle \|A-rd_\nu(A)\|_p\,\le\,\nu\,\|A\|_p,
Ax_i = b_i, \qquad i = 1,\ldots,k.
</math></center>
</math></center>


dla <math>\displaystyle p=1,\infty</math>, oraz
Układy równań z tą samą macierzą, ale ze  zmieniającą się prawą stroną równania
powstają często przy  rozwiązywaniu, np. równań różniczkowych cząstkowych,
gdzie  prawa strona układu odpowiada zmieniającym się warunkom  brzegowym.


<center><math>\displaystyle \|A-rd_\nu(A)\|_2\,\le\,\|A-rd_\nu(A)\|_E\,\le\,\nu\,\|A\|_E
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Wskazówka </span><div class="mw-collapsible-content" style="display:none">
    \,\le\,\sqrt{n}\,\nu\,\|A\|_2.
<div style="font-size:smaller; background-color:#efe"> Wystarczy tylko <strong>jeden raz</strong> wyznaczyć rozkład LU macierzy <math>\displaystyle A</math> </div>
</math></center>
</div></div>


</div></div>
</div></div>
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie </span><div class="mw-collapsible-content" style="display:none"><div style="margin-left:1em"> 
No tak, oczywiście,
wystarczy
{{algorytm|||
<pre>
Znajdź rozkład <math>\displaystyle PA = LU</math>;
Utwórz macierz <math>\displaystyle B = [b_1,\ldots,b_k]</math>;
Rozwiąż <math>\displaystyle LY = B</math>;
Rozwiąż <math>\displaystyle UX = Y</math>;
</pre>}}
Rozwiązanie każdego z układów <math>\displaystyle LY = B</math> i <math>\displaystyle UX = Y</math> można przyspieszyć, tworząc
warianty algorytmów podstawienia w przód i w tył, operujące od razu na wszystkich kolumnach macierzy <math>\displaystyle B</math>. Zaimplementowano je w LAPACKu, w tandemie
funkcji <code>DGETRF</code> (rozkład) i <code>DGETRS</code> (rozwiązanie) oraz w
<code>DGESV</code>, łączącej funkcjonalność obu.
</div></div></div>


<div style="margin-top:1em; padding-top,padding-bottom:1em;">
<div style="margin-top:1em; padding-top,padding-bottom:1em;">
<span  style="display: block; background-color:#fefeee; border-bottom: 1px solid #E5E5E5; line-height: 1.1em; padding-bottom: 0.2em; font-variant:small-caps; color:#1A6ABF;">Ćwiczenie</span>  
<span  style="display: block; background-color:#fefeee; border-bottom: 1px solid #E5E5E5; line-height: 1.1em; padding-bottom: 0.2em; font-variant:small-caps; color:#1A6ABF;">Ćwiczenie: Metoda Cholesky'ego</span>  
<div class="exercise">
<div class="exercise">


Czy algorytm eliminacji Gaussa dla <math>\displaystyle Ax=b</math>, gdzie macierz <math>\displaystyle A</math> jest <strong>symetryczna i dodatnio określona</strong>
Ważnym przykładem macierzy
zawsze da wynik <math>\displaystyle \widetilde{x}</math> o dużej dokładności, rzędu precyzji arytmetyki?
szczególnej postaci są macierze symetryczne i dodatnio określone.
Są to macierze spełniające warunki:


<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Wskazówka </span><div class="mw-collapsible-content" style="display:none">
<center><math>\displaystyle A=A^T \qquad  \mbox{oraz}  \qquad x^T A x\,>\,0,\quad\forall x\ne 0.
<div style="font-size:smaller; background-color:#efe"> Oceń wskaźnik wzrostu i skorzystaj z twierdzenia o numerycznej poprawności </div>
</math></center>
</div></div>
 
Dla takich macierzy można nieco zmniejszyć koszt kombinatoryczny
i zużycie pamięci przeprowadzając trochę inny rozkład na macierze trójkątne:
tak, aby otrzymać
rozkład 
 
<center><math>\displaystyle A\,=\,L\cdot D\cdot L^T
</math></center>
 
zamiast <math>\displaystyle PA=LU</math>, przy czym <math>\displaystyle L</math> jest tu jak zwykle macierzą
trójkątną dolną z jedynkami na przekątnej, a <math>\displaystyle D</math> jest macierzą
diagonalną z dodatnimi elementami na diagonali. Opracuj taki algorytm. W jego
implementacji możesz porównywać się z procedurą LAPACKa <code>DPOSV</code>.
Inny wariant tego samego
rozkładu to tak zwany rozkład Cholesky'ego--Banachiewicza, w którym przy tych
samych założeniach na <math>\displaystyle A</math>, szukamy rozkładu wykorzystującego tylko jedną macierz
trójkątną dolną:
 
<center><math>\displaystyle A\,=\,\widetilde{L}\cdot \widetilde{L}^T,
</math></center>


(oczywiście tym razem nie żądamy, aby <math>\displaystyle \widetilde{L}</math> miała na diagonali jedynki).
Jaka jest relacja między rozkładem <math>\displaystyle LDL^T</math> a <math>\displaystyle \widetilde{L}\widetilde{L}^T</math>?
</div></div>
</div></div>


<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie </span><div class="mw-collapsible-content" style="display:none"><div style="margin-left:1em">   
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie </span><div class="mw-collapsible-content" style="display:none"><div style="margin-left:1em">   
Dla naszej macierzy wskaźnik wzrostu <math>\displaystyle \rho_N \leq 2</math>, zatem algorytm eliminacji
Bez zmniejszenia ogólności rozpatrzymy
Gaussa jest numerycznie poprawny. Ale <strong>nie znaczy</strong> to, że da wynik <math>\displaystyle \widetilde{x}</math> taki, że
tylko pierwszy krok rozkładu <math>\displaystyle LDL^T</math>. W tym celu, zauważmy najpierw, że
<math>\displaystyle a_{1,1}= e_1^TA e_1>0</math> (gdzie <math>\displaystyle  e_1</math> jest pierwszym
wersorem), a więc nie musimy przestawiać wierszy,  
bo element na diagonali jest niezerowy. W pierwszym kroku mnożymy
macierz <math>\displaystyle A</math> z lewej strony przez odpowiednią macierz <math>\displaystyle L_1</math>, a potem
z prawej przez <math>\displaystyle L_1^T</math>. Kluczem do zrozumienia algorytmu jest uwaga,  
że efektem mnożenia macierzy <math>\displaystyle L_1 A</math> z prawej strony przez <math>\displaystyle L_1^T</math>
jest wyzerowanie elementów pierwszego wiersza poza <math>\displaystyle a_{1,1}</math>
i pozostawienie niezmienionych pozostałych elementów. Ponadto
macierz <math>\displaystyle A^{(1)}=L_1 AL_1^T</math> jest symetryczna i dodatnio określona.
Rzeczywiście,  
 
<center><math>\displaystyle \Big(A^{(1)}\Big)^T\,=\,(L_1AL_1^T)^T
    \,=\,(L_1^T)^TA^TL_1^T\,=\,L_1AL_1^T,
</math></center>


<center><math>\displaystyle \frac{||x-\widetilde{x}||}{||x||} \approx \nu.
oraz dla <math>\displaystyle  x\ne 0</math>
 
<center><math>\displaystyle x^T A^{(1)} x\,=\, x^T L_1 A L_1^T  x
    \,=\,(L_1^T x)^T A(L_1^T x)\,>\,0,
</math></center>
</math></center>


Ponieważ <math>\displaystyle \widetilde{x}</math> jest dokładnym rozwiązaniem równania z macierzą zaburzoną,
bo <math>\displaystyle x\ne 0</math> implikuje <math>\displaystyle L_1^T x\ne 0</math>. Stąd
<math>\displaystyle \widetilde{A}\widetilde{x} = b</math>, należy ocenić wpływ zaburzenia macierzy na jakość
<math>\displaystyle a^{(1)}_{2,2}= e_2^TA^{(1)} e_2>0</math>. Postępując tak
wyniku, a tu już wiemy, że jeśli zaburzenie jest dostatecznie małe (czyli, gdy
dalej otrzymujemy
precyzja arytmetyki jest dostatecznie duża), to


<center><math>\displaystyle \frac{||x-\widetilde{x}||}{||x||} \approx K\cdot  \mbox{cond} (A)\cdot \nu,
<center><math>\displaystyle L_{n-1}L_{n-2}\cdots L_2L_1AL_1^TL_2^T\cdots L_{n-2}^TL_{n-1}^T
    \,=\,D,
</math></center>
</math></center>


a więc <strong> błąd będzie mały tylko wtedy, gdy uwarunkowanie <math>\displaystyle A</math> będzie
przy czym macierz <math>\displaystyle D</math> jest diagonalna i dodatnio określona,
niewielkie!</strong> Dobrym przykładem jest tu macierz Hilberta, która jest symetryczna
a więc wyrazy na diagonali są dodatnie. Oznaczając
i dodatnio określona, lecz mimo to już dla średnich <math>\displaystyle N</math> algorytm eliminacji
 
Gaussa daje wyniki obarczone stuprocentowym błędem! Potwierdź to
<center><math>\displaystyle L\,=\,L_1^{-1}L_2^{-1}\cdots L_{n-1}^{-1}
eksperymentalnie!
</math></center>
 
dostajemy żądany rozkład.
 
Zauważmy, że przy praktycznej realizacji rozkładu <math>\displaystyle A=LDL^T</math>  
wystarczy modyfikować jedynie wyrazy pod i na głównej przekątnej
macierzy wyjściowej, ponieważ, jak zauważyliśmy, wszystkie kolejne
macierze <math>\displaystyle A^{(k)}</math> są symetryczne. Pozwala to zmniejszyć --- w stosunku do
rozkładu LU --- koszt
obliczeniowy o połowę, do <math>\displaystyle 2n^3/3</math> operacji arytmetycznych i zmieścić czynniki
rozkładu w pamięci przeznaczonej na przechowywanie istotnych elementów <math>\displaystyle A</math>
(czyli w dolnym trójkącie macierzy).
 
Oczywiście, <math>\displaystyle \widetilde{L} = L \sqrt{D}</math>.
 
</div></div></div>
</div></div></div>


<div style="margin-top:1em; padding-top,padding-bottom:1em;">
<div style="margin-top:1em; padding-top,padding-bottom:1em;">
<span  style="display: block; background-color:#fefeee; border-bottom: 1px solid #E5E5E5; line-height: 1.1em; padding-bottom: 0.2em; font-variant:small-caps; color:#1A6ABF;">Ćwiczenie: Numeryczne kryterium "numerycznej poprawności"</span>  
<span  style="display: block; background-color:#fefeee; border-bottom: 1px solid #E5E5E5; line-height: 1.1em; padding-bottom: 0.2em; font-variant:small-caps; color:#1A6ABF;">Ćwiczenie: Obliczanie wyznacznika macierzy</span>  
<div class="exercise">
<div class="exercise">


Jeśli
Bardzo rzadko w praktyce numerycznej zdarza się potrzeba obliczenia wartości
wyznacznika macierzy <math>\displaystyle A \in R^{N \times N}</math>. Zaproponuj metodę obliczania
<math>\displaystyle  \mbox{det} (A)</math> oraz wskaż, jakiego rodzaju problemy numeryczne możesz napotkać.
</div></div>


<center><math>\displaystyle  
<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Rozwiązanie </span><div class="mw-collapsible-content" style="display:none"><div style="margin-left:1em"> 
  (A+E) z\,=\, b,  
Wystarczy wykonać rozkład <math>\displaystyle PA=LU</math>. Wtedy
 
<center><math>\displaystyle \mbox{det}  A\,=\,(-1)^p u_{11}u_{22}\cdots u_{nn},  
</math></center>
</math></center>


gdzie <math>\displaystyle \|E\|_p\le K\nu\|A\|_p</math>,
gdzie <math>\displaystyle p</math> jest liczbą przestawień wierszy w eliminacji. Tak więc, koszt
to oczywiście dla residuum <math>\displaystyle r= b-A z</math> mamy
obliczenia wyznacznika to <math>\displaystyle O(N^3)</math>.
 
Kłopoty numeryczne możemy mieć z reprezentacją samej wartości wyznacznika w
arytmetyce zmiennoprzecinkowej. Ponieważ


<center><math>\displaystyle  
<center><math>\displaystyle \mbox{det}  (cA) = c^N A,
  \| r\|_p\,\le\,K\nu\|A\|_p\| z\|_p.
</math></center>
</math></center>


Pokazać, że dla <math>\displaystyle p=1,2,\infty</math> zachodzi też twierdzenie odwrotne, tzn.
to dość łatwo będzie trafić (przy większych niż średnich wartościach <math>\displaystyle N</math>) na wartości
jeśli spełniony jest powyższy warunek dla residuum, to istnieje macierz pozornych
wyznacznika, które będą albo potwornie duże, albo potwornie małe. Zresztą, zrób
zaburzeń <math>\displaystyle E</math> taka, że <math>\displaystyle \|E\|_p\le K\nu\|A\|_p</math> oraz spełniona jest
eksperyment:
równość <math>\displaystyle (A+E) z\,=\, b</math>.


Jest to tak zwane <strong>numeryczne kryterium "numerycznej poprawności"</strong>, bo (dla
Najpierw z losową macierzą <math>\displaystyle 100\times 100</math> i jej krotnościami...
konkretnych danych) pozwala, wyłącznie na podstawie obliczonych numerycznie
wartości ocenić zaburzenie pozorne macierzy, dla którego numeryczny wynik jest
dokładnym rozwiązaniem.


<div class="mw-collapsible mw-made=collapsible mw-collapsed"><span class="mw-collapsible-toogle mw-collapsible-toogle-default style="font-variant:small-caps">Wskazówka </span><div class="mw-collapsible-content" style="display:none">
<div class="output" style="background-color:#e0e8e8; padding:1em"><pre>
<div style="font-size:smaller; background-color:#efe"> Rozpatrzyć
<math>\displaystyle E= r( \mbox{sgn} \,z_i)_{1\le i\le n}^T/\| z\|_1</math> dla <math>\displaystyle p=1</math>,
octave:26> A=rand(100);
<math>\displaystyle E= r z^T/\| z\|_2^2</math> dla <math>\displaystyle p=2</math>, oraz
octave:27> det(A)
<math>\displaystyle E= r( \mbox{sgn} \,z_k) e_k^T/\| z\|_\infty</math> dla <math>\displaystyle p=\infty</math>,
ans = -7.5240e+24
gdzie <math>\displaystyle k</math> jest indeksem dla którego <math>\displaystyle |z_k|=\| z\|_\infty</math>. </div>
octave:28> det(10*A)
</div></div>
ans = -7.5240e+124
octave:29> det(100*A)
ans = -7.5240e+224
octave:30> det(1000*A)
ans = -Inf
</pre></div>
   
oraz z coraz większymi macierzami losowymi:


</div></div>
<div class="output" style="background-color:#e0e8e8; padding:1em"><pre>
octave:32> det(rand(100))
ans =  4.3751e+25
octave:33> det(rand(400))
ans = -3.0348e+218
octave:34> det(rand(800))
ans = -Inf
</pre></div>
Można sobie z
tym poradzić, szacując wpierw rząd wielkości wyznacznika, a następnie
reprezentując jego wartość w formie cecha--mantysa, np. jako parę liczb <math>\displaystyle m,c</math>
takich, że <math>\displaystyle  \mbox{det} (A) = m\cdot 10^c</math>, ale
dopasowując obie tak, by nie nastąpił ani nadmiar, ani niedomiar.
</div></div></div>

Wersja z 17:11, 2 wrz 2006


Ćwiczenia: rozwiązywanie układów równań liniowych

Ćwiczenie: Mnożyć przez odwrotność to nie zawsze to samo...

W Octave układ równań Ax=b rozwiązujemy korzystając z "operatora rozwiązywania równania"

 
x = A \ b;

W tym celu Octave oczywiście wewenętrznie wykorzystuje funkcję LAPACKa DGESV. Ale w Octave jest także funkcja inv, wyznaczająca macierz odwrotną, więc niektóre (nie najlepsze, oględnie mówiąc) podręczniki zalecają

 
x = inv(A)*b;

Przedyskutuj, które podejście jest lepsze i dlaczego. Przeprowadź eksperymenty numeryczne weryfikujące Twoją tezę.

Rozwiązanie

Ćwiczenie: Wybór elementu głównego jest naprawdę ważny!

Rozwiąż prosty układ równań

Parser nie mógł rozpoznać (nieznana funkcja „\aligned”): {\displaystyle \displaystyle \aligned 10^{-18} x_1 + x_2 &= 1,\\ x_1 + 2x_2 &= 4, \endaligned}

czterema sposobami:

  • na kartce papieru (dokładnie!)
  • w komputerze, w arytmetyce podwójnej precyzji, korzystając z rozkładu LU bez wyboru elementu głównego
  • jak poprzednio, ale z wyborem elementu głównego w kolumnie
  • w LAPACKu lub Octave.

Porównaj uzyskane rozwiązania i wyciągnij wnioski.

Rozwiązanie

Ćwiczenie

Zapisz w Octave algorytm rozkładu LU macierzy (bez wyboru elementu głównego) działający in situ.

Wskazówka

Wykorzystaj go do napisania funkcji, która rozwiąże układ równań Ax=b.

Przetestuj tę funkcję na kilku macierzach i porównaj czas jego działania z czasem wykonania operacji x = A\b.

Spróbuj zastosować swój algorytm do kilku specjalnych macierzy:

  • Hilberta dużego wymiaru
  • diagonalnej z jednym elementem bardzo małym (a nawet równym zero)
Rozwiązanie

Ćwiczenie: Czy Twoje programy działają naprawdę szybko?

Zrób zadanie poprzednie programując, niczym Zosia-Samosia, wszystko od początku do końca w czystym C. Porównaj czasy działania Twojego programu i programu wywołującego po prostu procedurę biblioteczną LAPACKa DGESV, najlepiej wspartą dobrze podrasowanymi BLASami.

Wskazówka
Rozwiązanie

Ćwiczenie: Układy równań z wieloma prawymi stronami

Podaj sposób taniego wyznaczenia rozwiązania sekwencji k<N układów równań z tą samą macierzą ARN×N i różnymi prawymi stronami:

Axi=bi,i=1,,k.

Układy równań z tą samą macierzą, ale ze zmieniającą się prawą stroną równania powstają często przy rozwiązywaniu, np. równań różniczkowych cząstkowych, gdzie prawa strona układu odpowiada zmieniającym się warunkom brzegowym.

Wskazówka
Rozwiązanie

Ćwiczenie: Metoda Cholesky'ego

Ważnym przykładem macierzy szczególnej postaci są macierze symetryczne i dodatnio określone. Są to macierze spełniające warunki:

A=ATorazxTAx>0,x0.

Dla takich macierzy można nieco zmniejszyć koszt kombinatoryczny i zużycie pamięci przeprowadzając trochę inny rozkład na macierze trójkątne: tak, aby otrzymać rozkład

A=LDLT

zamiast PA=LU, przy czym L jest tu jak zwykle macierzą trójkątną dolną z jedynkami na przekątnej, a D jest macierzą diagonalną z dodatnimi elementami na diagonali. Opracuj taki algorytm. W jego implementacji możesz porównywać się z procedurą LAPACKa DPOSV. Inny wariant tego samego rozkładu to tak zwany rozkład Cholesky'ego--Banachiewicza, w którym przy tych samych założeniach na A, szukamy rozkładu wykorzystującego tylko jedną macierz trójkątną dolną:

A=L~L~T,

(oczywiście tym razem nie żądamy, aby L~ miała na diagonali jedynki). Jaka jest relacja między rozkładem LDLT a L~L~T?

Rozwiązanie

Ćwiczenie: Obliczanie wyznacznika macierzy

Bardzo rzadko w praktyce numerycznej zdarza się potrzeba obliczenia wartości wyznacznika macierzy ARN×N. Zaproponuj metodę obliczania det(A) oraz wskaż, jakiego rodzaju problemy numeryczne możesz napotkać.

Rozwiązanie