2. Domácí úkol: Newtonův fraktál
Úvod
Cílem druhého úkolu je dále prozkoumat vlastní typy, práci s maticemi a poli a v neposlední řadě i s vizualizačními nástroji dostupnými v Julia.
Teoretický background
Newtonova metoda, také často označovaná jako Newtonova-Raphsonova metoda, má komplikovanou historii. V jisté formě ji zřejmě dokázali používat už Babyloňané, její moderní formulace pravděpodobně pochází až ze 17. století.
Dnes ji můžeme popsat snadno takto: pro komplexní funkci komplexní proměnné a zadané komplexní číslo (počáteční aproximace, počáteční podmínka) sestrojme rekurentní posloupnost
kde označuje derivaci funkce . Naším přáním je, aby takto zkonstruovaná posloupnost konvergovala k jistému bodu , který je nulovým bodem funkce , tj. .
Pokud je první aproximace (tj. ) blízko u nulového bodu funkce , pak lze takovéto chování očekávat. Ukazuje se, že v případě bodů vzdálenějších od nulových bodů je chování rekurentní posloupnosti popsané výše daleko zajímavější. Komplexní rovina se nerozdělí na souvislé oblasti konvergence, ale takovéto rozdělení vykazuje fraktální kvality. Ve skutečnosti není nijak zaručeno, že uvedená rekurentní posloupnost bude vždy konvergentní, nebo že ji vždy lze zkonstruovat (ve jmenovateli zlomku se může vyskytnout nula, můžeme se dostat mimo definiční obor funkce , atp.).
Naším cílem v tomto úkolu je tuto metodu implementovat a graficky prozkoumat její chování.
Implementační pokyny
Své řešení opět doplňte do souboru main.jl, který obsahuje hrubou kostru
řešení. Odpovídající testy naleznete v adresáři tests (můžete přidat případně
i své vlastní).
Typ Newton{T}
Objekty tohoto typu popisují obdélníkovou mřížku v komplexní rovině určenou
rozsahy re_min a re_max v reálné části a im_min a im_max v imaginární
části. V reálném směru máme na mřížce width bodů a podobně v imaginárním
směru height bodů. Komplexní čísla reprezentujeme typem Complex{T}, kde T
je podtyp typu AbstractFloat.
HINT: Kdyby například re_min = -1.0, re_max = 1.0, im_min = -1.0 a im_max = 1.0 a dále width = height = 3, pak uvažujeme body -1.0-1.0im, -1.0+0.0im, -1.0+1.0im, 0.0-1.0im, 0.0+0.0im, 0.0+1.0im, 1.0-1.0im, 1.0+0.0im a 1.0+1.0im.
Dále objekty tohoto typu nesou informaci o sledované funkci f a její derivace
df. Tyto funkce berou jeden (komplexní) argument a vrací jednu (komplexní)
hodnotu. Je na uživateli, aby zadal správné funkce, jejich vztah (jedna je
derivací druhé) nijak testovat nebudeme.
V neposlední řadě tyto objekty obsahují informaci o napočtených experimentech.
Počáteční podmínky pro iterování volíme jako body na uvedené mřížce a získané
informace budeme zaznamenávat do matic (viz dále, které indexujeme obvyklým
způsobem: bodu vlevo dole (tj. re_min + im_min * 1im) odpovídá index [1, 1] a
bodu vpravo nahoře (tj. re_max + im_max * 1im) odpovídá index [width, height].
Zaznamenáváme následující:
- matice
dataobsahuje limity (k detekci konvergence viz dále) posloupností s odpovídajícími počátečními podmínkami, nebo komplexníNaNv případě nedosažení konvergence, - matice
iterationsobsahuje provedený počet (Int64) iterací pro odpovídající počáteční podmínku, případně0pokud během iterování došlo k přetečení nebo se během iterování vyskytl komplexníNaN, nebo nastala výjimka. - matice
rootsobsahuje příslušnost odpovídající počáteční podmínky k hypotetickému nulovému bodu zkoumané funkce (ty indexujeme od1), případně0pokud pro tuto počáteční podmínku nedošlo k dosažení konvergence.
Konečně vektor roots_list komplexní čísel obsahuje přibližné hodnoty nalezených
nulových bodů (kořenů) uvažované funkce.
Pokud to bude potřeba, můžete si přidat i další pomocné či pracovní atributy.
Vnitřní konstruktor tohoto typu očekává popořadě funkci, její derivaci,
rozsahy reálných a komplexních složek. Rozměry mřižky lze předat jako
keyword argumenty a výchozí hodnota je nastavena na mřížku.
Tento konstruktor připraví místo pro výpočty (matice vhodných rozměrů, atd.),
ale ještě automaticky nespustí výpočet. Dále ověří, že rozsahy dávají smysl
(např. re_min není větší nebo rovno re_max) a že rozměry mřížky v každém
směru jsou alespoň . V případě nesplnění těchto podmínek vyvolá
ArgumentError.
Dále je vhodné pro tento nový typ implementovat show metodu, jinak bude
výpis objektu nepřehledný, zde nechávám volnost vaší kreativitě.
Metoda newton
Tato metoda je ústřední pro naše snažení a implementuje samotnou Newtonovu metodu. Přijímá popořade poziční argumenty
fadfpředstavující funkci a její derivaci, ty jsou schopny pracovat s komplexní proměnnou a vrací komplexní číslo,- komplexní číslo
zpředstavující počáteční podmínku (komplexní číslo typuS <: Complex),
a dále keyword argumenty
maxiterpředstavující maximální povolený počet iterací (výchozí hodnota1_000),atolje přesnost použitá pro rozhodnutí o konvergenci (viz dále; výchozí hodnota jesqrt(eps(abs(one(S))))).
Metoda implementuje Newtonovu metodu popsanou výše a ukončuje se následovně:
- Pokud se od sebe dvě poslední iterace v absolutní hodnotě neliší více
než o
atol, pak končíme úspěchem a vracíme dvojici, kde v první složce je hodnota poslední iterace a v druhé provedený počet iterací. - Pokud dosáhneme maximálního počtu iterací, pak vracíme dvojici, kde v první
složce je
S(NaN)a v druhé onen maximální počet iterací. - Pokud během výpočtu dojde v rámci aritmetických operací ve strojových číslech
k vytvoření
NaNneboInfhodnoty, nebo dojde k problému s vyhodnocováním funkčních hodnot zadaných funkcí, pak výpočet zcela selhal a vracíme dvojici, kde v první složce jeS(NaN)a v druhé0.
HINT: V kódu je možné pro testování různých NaN hodnot použít metodu isnan, podobně existuje isinf metoda.
Metoda compute!
Tato metoda přijímá objekt nt typu Newton{T} a jako keyword argumenty opět
maximální počet iterací maxiter (výchozí hodnota 1_000) a absolutní
toleranci atol. Tato metoda provádí finální výpočet ve dvou krocích:
- V každém bodě mřížky provede Newtonovu metodu a výsledek zapíše do
odpovídajícího prvku v maticích
dataaiterations(můžete přirozeně přímo použít metodunewtonzmíněnou výše, ale není to nutné). - Dále odhalí nalezené různé limitní body a to následujícím elementárním
způsobem: nalezené kořeny jsou přidávány do pole
roots_list, při procházení maticedataignorujemeNaNhodnoty, pokud se daný prvek maticedatališí od některého z již nalezených kořenů o méně nežsqrt(atol), pak je považujeme za totožné a do odpovídajícího prvku maticerootsvložíme index tohoto kořene (jakožto prvku poleroots_list). Pokud uvažovaný prvek maticedatanení dostatečně blízko žádnému z prvků poleroots_list, tak ho vložíme na konec tohoto pole jakožto nový kořen a do odpovídajícího prvku maticerootsopět zaznamenáme informaci o indexu.
Metoda vrací zpracovávaný objekt nt.
Metoda show
Zde vám ponechávám největší volnost. Metoda show přijme objekt typu
Newton{T} a vytvoří jeho grafickou reprezentaci pomocí vámi zvoleného grafického
balíčku (Images, PyPlot, atd., viz materiály).
Pro ilustraci přikládám obrázek na začátku tohoto zadání.
Typicky se při vizualizaci pro každý nalezený kořen volí odstín barvy a počet iterací pak modifikuje sytost barvy. K tomuto účelu se velmi hodí způsob zadání barvy pomocí HSV (hue, saturation, value). Ale nebojte se zkusit data obarvit dle své fantazie.
Pozor, tento krok pravděpodobně naruší testovací prostředí. Je potřeba do něj
příslušný balíček doinstalovat editací .gitlab-ci.yml souboru, kam před
spuštěním testů přidáme řádek tvaru
- julia -e 'import Pkg; Pkg.add("Images")'kde místo Images uvedete vámi použitý balíček. Pokud s tímto krokem budete
bojovat, tak stačí odevzdat i řešení, kde tento balíček nebudete explicitně
používat (zakomentování using …) a při kontrole si s tím poradím lokálně.
Uveďte tuto informaci v textu svého MR.
Tuto metodu vzhledem k její otevřenosti žádným způsobem netestuji v přiložených testech.
Ukázka použití této implementace je v notebooku examples.ipynb. Zkuste se
jím inspirovat a vytvořit vlastní zajímavý obrázek (jiná funkce, jiné barevné
znázornění, atp.). Z nejhezčích výtvorů vytvořím plakát pro zkrášlení šedých
fakultních chodeb.
Spouštění testů
Zadání je doplněno testy cílícími na vlastnosti typu Newton{T} a metod
newton a compute!.
$ julia test/runtests.jl
┌ Info:
│ Running tests in test_newton.jl...
└ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Test Summary: | Pass Total Time
Newton: constructor | 13 13 0.2s
Test Summary: | Pass Total Time
newton: bad cases | 10 10 0.1s
Test Summary: | Pass Total Time
newton: unique roots | 3 3 0.0s
Test Summary: | Pass Total Time
newton: multiple roots, starting nearby | 4 4 0.1s
Test Summary: | Pass Total Time
newton: iteration limit | 2 2 0.0s
Test Summary: | Pass Total Time
compute!: one root case | 7 7 0.4s
Test Summary: | Pass Total Time
compute!: two roots | 103 103 0.4s
Test Summary: | Pass Total Time
compute!: division by zero | 3 3 0.0sTyto testy se spouští i automaticky na Gitlabu a na stránce Merge Requestu jejich výsledek uvidíte ve formě červené nebo zelené ikonky.
Součástí vyhodnocení řešení bude porovnání rychlosti i robustnosti vaší implementace vzhledem k ostatními řešením.
Varování:
Pozor, pokud přidáváte grafických balíček pomocí using, tak ho přidejte do .gitlab-ci.yml, viz poznámku výše u metodu show.
Řešení a odevzdání
Opět vytvořte větev odvozenou z větve assignment/02-newton a nezvěte ji
solution/02-newton. Do solution/02-newton vložte své řešení editací
souboru main.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ě) s cílovou větví
assignment/02-newton a přiřaďte mě k němu jako assignee. Tímto aktem úkol
odevzdáte.