1. Domácí úkol: Gauss
Úvod
Prvním domácím úkolem se vrátíme k studentům velmi dobře známému lineárně-algebraickému algoritmu, ke Gaussově eliminační metodě (GEM). Obsahem tohoto úkolu cílíme zejména na následující body:
- procvičení základů Julia (podmínky, cykly, typy, metody, práce s poli/maticemi),
- oživení některých lineárně-algebraických znalostí (řešení soustav lineárních rovnic, inverze matice, GEM),
- názorná demonstrace nedostatků Gaussovy eliminační metody (viz
gauss.ipynb
).
Carl Friedrich Gauss byl jedním z největších matematiků a na jeho jméno narazíte ve spoustě matematických partií. V případě GEM nelze tvrdit, že by byl jejím prvním objevitelem. Jisté verze GEM existovaly již dříve a stopy vedou až k čínským matematikům do doby před naším letopočtem. Pravdou ovšem zůstává, že Gauss se touto metodou zabýval systematicky a to včetně důkazů. V literatuře občas také narazíte na alternativní název "Gauss–Jordanova eliminace."
Teoretický background
Implementační cíle úkolu jsou tři:
- funkce
echelon_form!
převádějící zadanou matici na redukovaný horní stupňovitý tvar (reduced echelon form), - funkce
inverse
invertující zadanou čtvercovou matici, - funkce
solve
hledající řešení zadané soustavy lineárních rovnic pomocí GEM.
Všechny z uvedených metod budou moci pracovat s různými číselnými datovými typy (strojová čísla, komplexní čísla, racionální čísla, modulární aritmetika).
Podrobně jsou tyto metody popsány dále v textu.
V implementaci funkcí inverse
a solve
půjde přirozeně využít funkci echelon_form!
.
Svůj kód umístěte do souboru gauss.jl
, kde je i předpřipravena nekompletní šablona.
V tomto souboru můžete (a je to velmi vhodné) definovat i další pomocné metody.
Případně můžete přidat i další testy do adresáře test
, viz sekci Lokální spouštění testů.
Než se pustíme do formálního popisu zadání, rychle připomeneme/představíme matematické pozadí problému.
Redukovaný horní stupňovitý tvar (HST)
Matice je v horním stupňovitém tvaru, právě když
- Zcela nulové řádky jsou jenom v dolní části matice.
- První (zleva doprava) nenulový prvek (pivot) v daném řádku je vždy v sloupcovém pořadí striktně za prvním nenulovým prvkem v předchozím řádku.
- Ve sloupci, kde je pivot (první nenulový prvek v daném řádku), jsou všechny prvky rovné nule vyjma řádku s pivotem, který je roven jedné.
Například následující matice je v redukovaném HST:
[
0 1 0 0 3 0
0 0 1 0 2 0
0 0 0 1 1 0
0 0 0 0 0 1
0 0 0 0 0 0
]
Gaussova eliminační metoda (GEM)
Ekvivalentní úpravy matice v Gaussově eliminační metodě jsou následující:
- prohození dvou řádků matice,
- vynásobení řádku matice nenulovým číslem,
- přičtení násobku jednoho řádku k jinému řádku.
Tyto úpravy nemění množinu všech řešení dané soustavy, proto se jim říká ekvivalentní úpravy.
GEM převede matici do redukovaného HST následujícím způsobem. Nejprve proveďme první krok:
- V prvním sloupci vyber pivot (viz níže) a prohoď řádek s pivotem a první řádek. Pokud je celý první sloupec nulový, tak se posuň do druhého kroku.
- Vynásob první řádek vhodným číslem tak, aby v prvním řádku a prvním sloupci (na místě pivotu) vznikla
1
. - Přičítáním vhodných násobků prvního řádku ke všem ostatním řádkům vytvoř v prvním sloupci pod pivotem nuly.
V druhém kroku se přesuneme do druhého sloupce:
- Pivot hledáme v druhém sloupci v řádcích pod řádkem s předchozím pivotem (pokud jsme ještě žádný nenalezli, tak ve všech řádcích). Pokud pivot nenajdeme, posouváme se do dalšího sloupce. Řádek s novým pivotem opět přesuneme do řádku hned pod řádkem s předchozím pivotem.
- Vynásobíme řádek s aktuálním pivotem vhodným číslem tak, aby na jeho místě vznikla
1
. - Přičítáním vhodných násobků vytvoříme nuly v druhém sloupci v řádcích pod i nad řádkem s pivotem.
Tento krok opakujeme v dalších sloupcích, dokud všechny neproběhneme. Na konci běhu algoritmu dostáváme matici v redukovaném HST. Algoritmus jistě skončí, protože matice má konečný počet sloupců.
Implementační pokyny
Naše implementace musí zvládnout pracovat s maticemi s prvky minimálně následujících typů
Float64
Complex{Float64}
BigFloat
Complex{BigFloat}
Int64
(zde viz poznámku níže)Rational{Int64}
AbstractGaloisField
zGaloisFields
balíčku
Pokud to bude možné, tak se budeme snažit počítat v exaktní aritmetice.
Vaše implementace by ale měla fungovat i s dalšími podobnými typy.
Nezapomínejte, že máte k dispozici abstraktní typy jako AbstractFloat
nebo Integer
.
Volba pivotu
Z popisu algoritmu v předchozích odstavcích je patrná nutnost umět rozhodnout o nenulovosti prvku matice, resp. čísla daného typu. Rozlišíme dvě situace:
- V případě čísel typů s nimiž počítáme v exaktní aritmetice (bez numerických chyb; tj.
Int64
,Rational{Int64}
aAbstractGaloisField
) za nenulové číslo bereme libovolné číslo daného typu různé od0
(ve smyslu!=
). - U čísel typů založených na číslech s pohyblivou desetinnou čárkou (tj.
Float64
,Complex{Float64}
,BigFloat
aComplex{BigFloat}
) za nulová budeme považovat všechna čísla v absolutní hodnotě menší nebo rovno2eps(T)
, kdeT
je buď přímo nějaký float nebo vystupuje jako float vComplex{T}
.
Volba pivotu není úplně triviální, existuje více strategií. My opět chování rozdělíme podle typu prvků matice s kterou počítáme.
- Pokud počítáme v exaktní aritmetice (
Int64
,Rational{Int64}
aAbstractGaloisField
), pak jako pivot volíme první nenulové číslo, které připadá v úvahu. - Pokud počítáme se čísly založenými na strojových číslech (
Float64
,Complex{Float64}
,BigFloat
aComplex{BigFloat}
), pak jako pivot volíme číslo s největší možnou absolutní hodnotou.
echelon_form!
Funkce echelon_form!
převede zadanou matici A
do redukovaného HST.
Funkce modifikuje vstup (pokud to lze) a současně i vrátí příslušnou matici v redukovaném HST.
Pokud je na vstupu matice, jejíž prvky jsou typu T
, v kterém nejsme vždy schopni provádět dělení (podtypy abstraktního typu Integer
), pak funkce vytvoří kopii zadané matice a přetypuje její prvky na Rational{T}
.
Tímto způsobem budeme stále počítat v exaktní aritmetice.
Jednoduchý příklad použití (další naleznete ve složce test
):
julia> A = [1 2 3; 4 5 6];
julia> echelon_form!(A)
2×3 Matrix{Rational{Int64}}:
1//1 0//1 -1//1
0//1 1//1 2//1
HINT: Funkci echelon_form!
můžete přidat i vhodné nepovinné argumenty, pomocí kterých můžete předávat informaci například metodě solve
…
inverse
Funkce inverse
pro čtvercovou matici A
vrátí její inverzní matici.
Pokud matice A
není čtvercová, nebo není regulární, pak vyhodí výjimku ErrorException
(viz metodu error
).
K výpočtu inverzní matice použijeme metodu echelon_form!
a standardní algoritmus známý z BI-LIN (převod matice (A|E)
na matici (E|A^(-1))
).
Například:
julia> A = [1.0 1.0 -1.0; 1.0 -1.0 1.0; -1.0 1.0 1.0];
julia> inverse(A)
3×3 Matrix{Float64}:
0.5 0.5 0.0
0.5 0.0 0.5
0.0 0.5 0.5
solve
Funkce solve
bere dva argumenty, matici A
a vektor b
, a vrátí nějaké řešení soustavy s maticí A
a pravou stranou b
.
Pokud matice A
a vektor b
nemají správné rozměry, nebo soustava nemá řešení, pak opět dostaneme výjimku ErrorException
.
K výpočtu lze zase velmi přirozeně použít metodu echelon_form!
.
Například:
julia> A = [1.0 1.0 -1.0; 1.0 -1.0 1.0; -1.0 1.0 1.0]; julia> b = [1.0, 1.0, 1.0]; julia> solve(A, b) 3-element Vector{Float64}: 1.0 1.0
Lokální spouštění testů
Toto zadání je úmyslně vytvořeno lehce "na koleni" a nevyužívá například moduly nebo Julia projekty ke správě závislostí (látka, ke které se dostaneme později během semestru).
K lokálnímu spuštění budete proto pravděpodobně potřebovat doinstalovat balíčky Glob
a GaloisFields
.
import Pkg; Pkg.add("Glob"); Pkg.add("GaloisFields")
Poté v kořenovém adresáři tohoto zadání stačí z příkazové řádky spustit
$ julia --color=yes test/runtests.jl
Tímto příkazem spustíte všechny test v souborech test/test_*.jl
.
Případně tak do adresáře test
můžete snadno přidat i další pomocné vlastní testy vlastních pomocných metod, jen je musíte pojmenovat ve tvaru test_*.jl
.
Pokud vše dopadne dobře, měli byste vidět následující standardní výstup:
$ julia test/runtests.jl
┌ Info: Running tests in test_echelon_form.jl...
└ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Test Summary: | Pass Total
echelon_form!: integer and trivial examples | 8 8
Test Summary: | Pass Total
echelon_form!: over rational numbers | 4 4
Test Summary: | Pass Total
echelon_form!: GaloisField, GF(5) | 4 4
Test Summary: | Pass Total
echelon_form!: Float64 | 3 3
Test Summary: | Pass Total
echelon_form!: BigFloat | 3 3
┌ Info: Running tests in test_inverse.jl...
└ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Test Summary: | Pass Total
inverse: non-invertible matrices | 3 3
Test Summary: | Pass Total
inverse: computation over rational field, small examples | 3 3
Test Summary: | Pass Total
inverse: computation in floating point arithmetic | 2 2
┌ Info: Running tests in test_other.jl...
└ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
...
...
...
┌ Info: Running tests in test_solve.jl...
└ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Test Summary: | Pass Total
solve: non-solvable examples | 2 2
Test Summary: | Pass Total
solve: simple solvable examples | 4 4
Test Summary: | Pass Total
solve: a badly conditioned system | 1 1
Test Summary: | Pass Total
solve: randomized tests | 3 3
V opačném případě dostanete od Julia vynadáno!
Řešení a odevzdání
Opět vytvořte větev odvozenou z větve assignment/01-gauss
a nezvěte ji například solution/01-gauss
.
Do solution/01-gauss
vložte své řešení editací souboru gauss.jl
případně přidáním testů do složky test
.
Až budete se svým řešením spokojeni, vytvořte MR (to můžete i dříve, aspoň uvidíte výsledek testů, pokud je nespouštíte lokálně) a přiřaďte mě k němu jako assignee
.
Tímto aktem úkol odevzdáte.
Poznámky
GaloisFields
Pro práci s konečnými tělesy zvolíme balíček GaloisFields
.
Nebudeme z něj potřebovat nic složitého, jen počítání v konečných tělesech (sčítání a násobení modulo zadané prvočíslo).
Práce s ním je jednoduchá:
julia> using GaloisFields; import GaloisFields: AbstractGaloisField
julia> Z5 = GaloisField(5)
𝔽₅
julia> m1 = Z5[1 2; 3 4]
2×2 Matrix{𝔽₅}:
1 2
3 4
julia> m1 * m1
2×2 Matrix{𝔽₅}:
2 0
0 2
julia> m1 + m1
2×2 Matrix{𝔽₅}:
2 4
1 3
julia> Z5 <: AbstractGaloisField
true
Import abstraktního typu je už proveden v šabloně pro řešení. Další příklady lze nalézt v testech.