Rozwiązywanie układów równań

Będzie nam potrzebny cały kod z implementacją macierzy z poprzednich zajęć. Jeżeli nie było Cię na poprzednich zajęciach, nie udało Ci się dokończyć wszystkich zadań lub masz wątpliwości, czy twoje rozwiązania są poprawne, możesz pobrać stąd naszą implementację klasy Matrix.

Następnie obok pliku matrices.py z implementacją macierzy z poprzednich labów utwórz plik gauss.py z poniższym kodem:

from matrices import Matrix, read_matrix_file, write_matrix_file
import sys

def reduce_to_echelon(m):
    ... # TODO: funkcja schodkująca macierze

def compute_solution(echelon_m):
    ... # TODO: funkcja wyliczająca rozwiązanie z wyschodkowanej macierzy

if __name__ == "__main__" and len(sys.argv) >= 2:
    m = read_matrix_file(sys.argv[1])
    reduce_to_echelon(m)
    sol = compute_solution(m)
    print(f"Rozwiązanie: {sol}")
    if len(sys.argv) >= 3:
        write_matrix_file(m, sys.argv[2])

Celem na dziś będzie napisanie programu rozwiązującego układ równań:

\[\begin{split} \begin{cases} m_{1\,1} x_1 + m_{1\,2} x_2 + \dots + m_{1\,n} x_n = m_{1\,n+1} \\ m_{2\,1} x_1 + m_{2\,2} x_2 + \dots + m_{2\,n} x_n = m_{2\,n+1} \\ \dots \\ m_{n\,1} x_1 + m_{n\,2} x_2 + \dots + m_{n\,n} x_n = m_{n\,n+1} \\ \end{cases} \end{split}\]

Czyli układu opisanego macierzą:

\[\begin{split} M = \begin{cases} m_{1\,1} & m_{1\,2} & \cdots & m_{1\,n} & m_{1\,n+1} \\ m_{2\,1} & m_{2\,2} & \cdots & m_{2\,n} & m_{2\,n+1} \\ \vdots & \vdots & \ddots & \vdots & \vdots \\ m_{n\,1} & m_{n\,2} & \cdots & m_{n\,n} & m_{n\,n+1} \\ \end{cases} \end{split}\]

Napiszemy do tego dwie funkcje:

  • retuce_to_echelon: która doprowadzi macierz do postaci schodkowej (nie zredukowanej).

  • compute_solution: która dla macierzy w postaci schodkowej obliczy z niej rozwiązanie.

Wtedy przy pomocy powyższego programu będziemy mogli rozwiązywać układy równań:

$ python gauss.py plik_z_macierza.txt

Ważne

Jeżeli w którymkolwiek momencie coś nie będzie Ci działać, możesz użyć funkcji write_matrix_file(m), żeby wypisać macierz m i zobaczyć jej aktualną zawartość. Najlepiej używaj z dodatkowym print przed wywołaniem, żeby odróżniać kolejne wypisywane wersje macierzy, jeżeli wypisujesz ją w pętli.

Schodkowanie macierzy

Zajmiemy się teraz implementacją metody Gaussa.

Zadanie 1: redukcja kolumny

Przetestuj swoją funkcję na jakiejś przykładowej macierzy.

Zadanie 2: bardzo naiwny Gauss

Zadanie 3: pół-pełny Gauss

Żeby uniknąć dzielenia przez zero (o ile to w ogóle możliwe — może się okazać, że rozwiązanie nie jest jednoznaczne i w którymś momencie dostaniemy same zera). Przykładowo mając w drugim kroku (kolumna o indeksie 1) macierz:

\[\begin{split}\begin{pmatrix} 1 & 20 & 4 & 8 & 7 & 10 \\ 0 & 1 & 5 & 2 & 7 & -3 \\ 0 & -6 & 3 & 2 & 1 & 0 \\ 0 & 5 & 2 & 1 & 1 & -7 \end{pmatrix}\end{split}\]

Zanim wywoła reduce_column_down(m, 1) powinna wykonać m.swap_rows(1, 2), ponieważ ze wszystkich wierszy jeszcze dostępnych trzeci ma największą wartość bezwzględną elementu w tej kolumnie. (Pierwszy wiersz już nie może zostać użyty, chociaż ma 20).

Obliczanie rozwiązania z wyschodkowanej macierzy

Zadanie 4: obliczanie pojedynczej zmiennej

Zaczniemy od funkcji, która mając obliczone wartości zmiennych \(x_{k+1}, \dots, x_n\) oraz pojedyncze równanie w już wyschodkowanej macierzy obliczy z niego kolejną zmienną. Ta sytuacja jest przedstawiona na rysunku:

Ilustracja obliczania pojedynczej zmiennej

Podświetlony wiersz odpowiada równaniu:

\[ 0x_1 + 0x_2 + \dots + m_{k\,k} x_k + m_{k\,k+1} x_{k+1} + m_{k\,k+2} x_{k+2} + \dots + m_{k\,n} x_n = m_{k\,n+1} \]

Na tym etapie rozwiązywania układu równań wartości zmiennych \(x_{k+1}, \dots, x_n\) są już wyliczone. Zatem kolejną zmienną \(x_k\) wyliczamy jako:

\[ x_k = \frac{1}{m_{k\,k}}\left(m_{k\,n+1} - m_{k\,k+1} x_{k+1} - m_{k\,k+2} x_{k+2} - \dots - m_{k\,n} x_n\right) \]

W programie współczynniki będziemy brać z macierzy — \(m_{k\,j} = \texttt{m.get(k - 1, j - 1)}\). Wartości zmiennych będziemy pobierać i zapisywać do pojedynczej listy \(x_j = \texttt{sol[j - 1]}\).

Później możemy kilka razy wywołać tę funkcję, aby wyliczyć całe rozwiązanie układu z wyschodkowanej macierzy:

m = Matrix(3, 4)
m.set(0, 0, 1); m.set(0, 1, 5); m.set(0, 2, 4); m.set(0, 3, 9)
m.set(1, 1, 1); m.set(1, 2, -3); m.set(1, 3, 2)
m.set(2, 2, 1); m.set(2, 3, -1)
sol = [None] * 3  # Na początek nie znamy żadnej z trzech zmiennych
sol[2] = extend_solution(m, 2, sol)
sol[1] = extend_solution(m, 1, sol)
sol[0] = extend_solution(m, 0, sol)
if sol == [18, -1, -1]:
    print("extend_solution: OK")
else:
    print("extend_solution: ERROR")

Zadanie 5: obliczanie wszystkich zmiennych

Ilustracja obliczania pojedynczej zmiennej

Poniżej masz kilka macierzy schodkowych, których możesz użyć do przetestowania:

  1. Skopiuj i zapisz wartości macierzy do pliku tekstowego. Nie kopiuj pierwszej linijki z rozwiązaniem.

  2. Wykonaj polecenie w terminalu: python gauss.py <nazwa_pliku>.

  3. Porównaj otrzymane rozwiązanie.

# Rozwiązanie: [7]
1 7
# Rozwiązanie: [4, 2]
1  3  10
0  1   2
# Rozwiązanie: [0, 3, 2]
1  2  1   8
0  1  4  11
0  0  1   2
# Rozwiązanie: [2, -1, 3, 1, 3]
1  1  2  0  1   10
0  1  3  1  2   15
0  0  1  2  1    8
0  0  0  1  3   10
0  0  0  0  1    3
# Rozwiązanie: [-11868, 4832, -1446, 249, -55, 18, -1, -1]
1  2  -1   3   0   1  4  -2    5
0  1   3  -2   1   4  0   1   12
0  0   1   5  -3   2  1   3   -4
0  0   0   1   4  -1  2   2    7
0  0   0   0   1   3  0  -1    0
0  0   0   0   0   1  5   4    9
0  0   0   0   0   0  1  -3    2
0  0   0   0   0   0  0   1   -1

Zadania domowe