1 Commits
Author SHA1 Message Date
nikola 67e98ed77c ai 2026-08-23 11:52:44 +02:00
+156 -162
View File
@@ -205,7 +205,7 @@ V nadaljevanju bomo predstavili osnovne koncepte za generiranje in evolucijo fra
#pagebreak() #pagebreak()
= OPIS APLIKACIJE = OPIS APLIKACIJE
Treender je aplikacija, ki nam omogoča, da izbiramo in ocenjujemo fraktalna drevesa, ki se na podlagi naših ocen razvijajo in prilagajajo našim okusom. V desnem zgornjem kotu nam prikaže všečnost, na dnu nam pa prikaže generacijo evolucije in kateri vzorec se prikazuje (@aplikacija-slika-def). Če povlečemo po zaslonu levo se nam v zgornjem levem kotu prikaže napis "DISLIKE" (@aplikacija-slika-dislike), v desno pa se nam prikaže napis "LIKE" (@aplikacija-slika-like). Treender je aplikacija, ki nam omogoča, da izbiramo in ocenjujemo fraktalna drevesa, ki se na podlagi naših ocen razvijajo in prilagajajo našim okusom. V desnem zgornjem kotu prikazuje odstotek, ki pove, kako zelo so si prikazana drevesa med seboj podobna, torej kako zelo se je izbira zožila; v vmesniku je označen kot všečnost, kar je natančneje pojasnjeno v @poglavje_podobnost. Na dnu prikazuje generacijo evolucije in zaporedno številko prikazanega vzorca (@aplikacija-slika-def). Če povlečemo po zaslonu levo se nam v zgornjem levem kotu prikaže napis "DISLIKE" (@aplikacija-slika-dislike), v desno pa se nam prikaže napis "LIKE" (@aplikacija-slika-like).
#grid( #grid(
columns: 3, columns: 3,
@@ -230,7 +230,42 @@ Treender je aplikacija, ki nam omogoča, da izbiramo in ocenjujemo fraktalna dre
], ],
) )
Ko vzorec ocenimo, si aplikacija zapomni, in ko ocenimo vse vzorce, se nam prikaže črna slika z napisom "new generation". V tem trenutku se glede na ocenitve ustvari nova generacija in izračuna všečnost. Ko vzorec ocenimo, si aplikacija oceno zapomni, in ko ocenimo vse vzorce, se prikaže črna slika z napisom "new generation". V tem trenutku se glede na ocene ustvari nova generacija in izračuna podobnost med novimi drevesi.
== Potek seje <potek_seje>
Uporabnik od aplikacije ne dobi nobenih drsnikov ali številčnih nastavitev; edino, kar počne, je, da vsako prikazano drevo s potegom levo ali desno oceni z "ne" ali "da". Ena poteza je torej ena binarna ocena, ne vnos podatkov. Prav v tem je namen aplikacije: uporabniku ni treba znati opisati, kaj mu je všeč, dovolj je, da med prikazanimi drevesi prepozna tisto, kar mu je bližje.
Generacija obsega 16 dreves, zato uporabnik z 16 potezami zaključi eno generacijo in vidi učinek svojih ocen. Število generacij ni omejeno, seja traja toliko časa, kolikor uporabnik želi. Seje, ki jih analiziramo v @poglavje_podobnost, so bile prekinjene po dvanajsti generaciji, kar pomeni 13 generacij in 208 potez na sejo.
@gen0_slika prikazuje vseh 16 dreves začetne, naključne generacije, @gen12_slika pa vseh 16 dreves dvanajste generacije iste seje. Razlika med njima je hkrati odgovor na vprašanje, kaj je rezultat postopka: uporabnik na koncu ne dobi enega drevesa, ampak generacijo dreves z enotnim značajem, iz katere si izbere posamezno sliko. Slike vseh vmesnih generacij so v prilogi.
#grid(
columns: 2,
gutter: 10pt,
[
#figure(
image("assets/full_generation/1761153347_0.webp"),
caption: [Generacija 0: naključna začetna populacija],
)<gen0_slika>
],
[
#figure(
image("assets/full_generation/1761153347_12.webp"),
caption: [Generacija 12 iste seje],
)<gen12_slika>
],
)
== Raznolikost izhodnega prostora <raznolikost>
Ker uporabnik na videz drevesa vpliva samo z izbiranjem, je smiselno vprašanje, kako širok je nabor slik, med katerimi izbira. Videz slike v celoti določa 180-bajtni genom (@dna_objekt), ki ga sestavljajo:
- 16 genov za vsako od 8 globin drevesa, ki določajo barvo veje in njeno spreminjanje po dolžini, debelino, dolžino, število otrok in razpon kota med njimi;
- tri naključna semena za obliko gora, razporeditev zvezd in variacije vej;
- štirje geni za položaj in velikost sonca oziroma lune ter za barvno shemo ozadja.
Ker vsaka veja glede na gen `branchCount` požene dve ali tri veje, ima drevo pri osmih globinah med $2^8 = 256$ in $3^8 = 6561$ končnih vej, barva pa se lahko med globinami spreminja neodvisno, kar daje razpon od enobarvnih do izrazito pisanih dreves. Ozadje ima 13 barvnih shem (tri dnevne palete v štirih odtenkih ter nočna) in znotraj vsake zvezno spremenljivo obliko gorovja, položaj ter velikost sonca oziroma lune.
Kolikšna raznolikost je s tem dejansko dosegljiva, je najbolje razvidno iz @gen0_slika: 16 naključnih genomov da drevesa od redkih in temnih do gostih in svetlih, z dnevnim, večernim in nočnim ozadjem. Prav tako pa je iz slike razvidna omejitev, ki je genom ne odpravi: motiv ozadja je vedno isti, torej nebo, sonce ali luna in tri plasti gora, spreminjata se le barvna shema in oblika gora. Aplikacija tako pokriva širok razpon dreves znotraj enega samega slikarskega motiva.
#pagebreak() #pagebreak()
@@ -239,158 +274,36 @@ Ko vzorec ocenimo, si aplikacija zapomni, in ko ocenimo vse vzorce, se nam prika
V tem poglavju na kratko predstavimo tehnologije, ki smo jih uporabili, in razloge za njihovo izbiro. V tem poglavju na kratko predstavimo tehnologije, ki smo jih uporabili, in razloge za njihovo izbiro.
== C++ == C++
C++ je visokonivojni programski jezik, ki je nastal kot razširitev jezika C. Razvil ga je Bjarne Stroustrup v zgodnjih 1980. letih z namenom, da združi moč in učinkovitost jezika C z objektno usmerjenimi koncepti, ki podpirajo boljšo organizacijo in ponovno uporabo kode @wiki_cpp. C++ je programski jezik, ki je nastal kot razširitev jezika C in ga je v zgodnjih osemdesetih letih razvil Bjarne Stroustrup. Združuje učinkovitost in nizkonivojni dostop do pomnilnika z objektno usmerjenimi koncepti ter obsežno standardno knjižnico @wiki_cpp.
=== Glavne značilnosti C++: Zanj smo se odločili iz dveh razlogov. Prvi je hitrost izrisa: drevo je sestavljeno iz več deset tisoč risalnih klicev na sliko (@optimizacija_izrisa), kar na mobilni napravi zahteva jezik brez samodejnega čiščenja pomnilnika. Drugi je neposreden dostop do pomnilniške predstavitve podatkov, ki ga izkoriščamo pri delu z genomom: celoten genom obravnavamo kot zaporedje bajtov, zaradi česar so križanje, kloniranje in mutacija preproste zanke čez to zaporedje (@zapis_dna).
1. *Objektno usmerjeno programiranje (OOP)*: C++ podpira objektno usmerjeno programiranje, kar omogoča uporabo razredov in objektov za modeliranje realnih svetovnih problemov. To olajša organizacijo kode in omogoča ponovno uporabo kode. == Raylib
Raylib je odprtokodna knjižnica za razvoj iger in multimedijskih aplikacij, ki ponuja preprost vmesnik za 2D- in 3D-grafiko, zvok in vhodne naprave ter podpira več platform, med njimi tudi Android @web_raylib @web_raylib_cheatsheet.
2. *Učinkovitost*: C++ je znan po svoji visoki učinkovitosti in hitrosti izvajanja, kar ga naredi primernega za razvoj aplikacij, ki zahtevajo visoko zmogljivost, kot so igre, sistemsko programiranje in razvoj vgrajenih sistemov. Izbrali smo jo, ker potrebujemo le nekaj osnovnih risalnih funkcij (krog, trikotnik, pravokotnik z barvnim prehodom, izris v teksturo) in obravnavo dotika, vse to pa ponuja brez obsežnega ogrodja. Pomembna je bila tudi možnost, da isto kodo prevedemo za namizje in za Android, kar je omogočilo razvoj in razhroščevanje na računalniku.
3. *Nizkonivojni dostop*: C++ omogoča dostop do spomina in drugih sistemskih virov na nizki ravni, kar omogoča večjo kontrolo nad strojno opremo. To je posebno koristno v sistemskem programiranju in razvoju vgrajenih sistemov. == Dear ImGui
Dear ImGui je odprtokodna knjižnica za hitro izdelavo uporabniških vmesnikov v aplikacijah, ki tečejo v realnem času. Deluje po načelu neposrednega načina (immediate mode), pri katerem se vmesnik izriše na novo v vsakem okvirju, kar poenostavi njegovo spreminjanje @git_imgui @git_rlImgui.
4. *Standardna knjižnica (STL)*: C++ vsebuje bogato standardno knjižnico, ki vključuje različne strukture podatkov (npr. vektorji, seznami, množice) in algoritme, ki olajšajo razvoj. V aplikaciji same ne uporabljamo. Uporabili smo jo za razvijalsko orodje, s katerim smo pregledovali seje, shranjene v podatkovni bazi, in izrisovali drevesa posameznih generacij; slike generacij v tej nalogi so nastale s tem orodjem.
5. *Preprostost in moč*: C++ združuje preprostost jezika C z dodatnimi zmožnostmi, kot so razredi, dedovanje, polimorfizem in preobremenjevanje operatorjev, kar omogoča večjo fleksibilnost in moč pri programiranju. == CMake
CMake je orodje za avtomatizacijo gradnje, ki iz opisa projekta v datotekah `CMakeLists.txt` generira gradbene datoteke za posamezno okolje, na primer `Makefile` ali projekt za Visual Studio, ter skrbi za iskanje in povezovanje odvisnosti @web_cmake @wiki_cmake.
=== Uporaba C++: Do njega smo prišli po ovinku: sprva smo uporabljali lastno gradbeno skripto, ki je sčasoma zahtevala več vzdrževanja kot sam projekt (@gradilni_sistem). CMake je to delo prevzel in nam omogočil gradnjo za Linux, Windows in Android z enim opisom projekta, hkrati pa sam prenese in zgradi Raylib.
- *Razvoj iger*: Zaradi svoje visoke učinkovitosti in možnosti za nizkonivojni dostop je \ C++ priljubljen jezik za razvoj iger. == SQLite
- *Sistemsko programiranje*: Uporablja se za razvoj operacijskih sistemov, gonilnikov in drugih sistemskih programov. SQLite je lahka vdelana relacijska podatkovna baza, ki teče kot del programa in ne potrebuje ločenega strežnika; celotna baza je shranjena v eni datoteki, podpira večino standardnega SQL in zagotavlja transakcijsko varnost @web_sqlite @wiki_sqlite.
- *Vgrajeni sistemi*: Zaradi svoje učinkovitosti in možnosti za nizkonivojni dostop je C++ priljubljen za razvoj vgrajenih sistemov.
- *Računalniška grafika*: Uporablja se za razvoj grafičnih aplikacij in orodij za obdelavo slik.
- *Znanstvene in inženirske aplikacije*: Zaradi svoje zmogljivosti in učinkovitosti se uporablja za razvoj znanstvenih in inženirskih aplikacij, ki zahtevajo visoko zmogljivost.
#pagebreak() Uporabljamo jo na strežniku, kjer hranimo ocene, ki jih odjemalci pošljejo ob koncu generacije, in v razvijalskem orodju za pregled teh podatkov. Za tak obseg podatkov je ločen strežnik za bazo nepotreben, ena datoteka pa poenostavi tudi izdelavo varnostnih kopij. Stanje seje na napravi uporabnika hranimo ločeno od nje, v lastnem binarnem zapisu, saj gre za eno samo strukturo fiksne velikosti.
== RAYLIB
Raylib je odprtokodna knjižnica, namenjena predvsem razvoju iger in multimedijskih aplikacij. Zasnovana je tako, da je enostavna za uporabo in omogoča hitro ustvarjanje 2D in 3D grafike, zvoka, vhodnih naprav in drugih multimedijskih vsebin @web_raylib @web_raylib_cheatsheet.
=== Glavne značilnosti Raylib:
1. *Enostavnost uporabe*: Raylib je zasnovan tako, da je enostaven za uporabo, z minimalnim številom funkcij, ki so potrebne za osnovno delovanje igre. To omogoča hitrejši razvoj in lažje učenje.
2. *Podpora za 2D in 3D grafiko*: Raylib podpira tako 2D kot 3D grafiko, kar omogoča razvoj različnih vrst iger in aplikacij.
3. *Vhodne naprave*: Podpira različne vhodne naprave, kot so tipkovnica, miška, igralni nadzorniki in zaslon na dotik.
4. *Zvok*: Vključuje osnovno podporo za predvajanje zvoka in glasbe, kar je pomembno za razvoj iger.
5. *Preprostost vključevanja*: Raylib je zasnovan kot enostavna knjižnica, ki se lahko vključi v projekt z minimalnimi nastavitvami.
6. *Odprtokodnost*: Ker je Raylib odprtokoden, ga lahko razvijalci prilagajajo svojim potrebam in prispevajo k njegovemu razvoju.
=== Uporaba Raylib:
- *Razvoj iger*: Raylib je priljubljen med razvijalci iger, saj omogoča hitro ustvarjanje prototipov in enostavno upravljanje z grafiko, zvokom in vhodnimi napravami.
- *Izobraževanje*: Zaradi svoje enostavnosti in odprtokodnosti je Raylib pogosto uporabljen v izobraževalnih nameneh za učenje osnov razvoja iger.
- *Multimedijske aplikacije*: Uporablja se za razvoj različnih multimedijskih aplikacij, ki zahtevajo upravljanje z grafiko in zvokom.
Raylib je zaradi svoje preprostosti in zmogljivosti priljubljen izbir za razvijalce, ki želijo hitro ustvariti igre ali multimedijske aplikacije brez potrebe po kompleksnih orodjih in knjižnicah.
#pagebreak()
== ImGui
Dear ImGui (ali preprosto ImGui) je odprtokodna knjižnica, ki omogoča hitro in enostavno ustvarjanje uporabniških vmesnikov za aplikacije v realnem času. Zasnovana je za razvijalce, ki potrebujejo preprost in učinkovit način za dodajanje uporabniških vmesnikov v svoje aplikacije, predvsem v igrah, orodjih in drugih interaktivnih programih @git_imgui @git_rlImgui.
=== Glavne značilnosti ImGui:
1. *Enostavnost uporabe*: ImGui je zasnovan tako, da je enostaven za uporabo in integracijo v obstoječe projekte. Njegova preprosta arhitektura omogoča hitro ustvarjanje in spreminjanje uporabniških vmesnikov.
2. *Učinkovitost*: Zasnovana je za visoko učinkovitost, kar je posebno pomembno za aplikacije v realnem času, kot so igre in simulacije.
3. *Immediate Mode GUI*: ImGui uporablja koncept "immediate mode" GUI, kar pomeni, da se uporabniški vmesnik ustvari in posodobi vsak frame. To omogoča veliko fleksibilnost in enostavnost pri spreminjanju vmesnika v realnem času.
4. *Podpora za različne platforme*: ImGui je prenosljiv in podpira več platform in grafičnih API-jev, kot so OpenGL, DirectX, Vulkan in Metal.
5. *Odprtokodnost*: Ker je ImGui odprtokoden, ga lahko razvijalci prilagajajo svojim potrebam in prispevajo k njegovemu razvoju.
=== Uporaba ImGui:
- *Razvoj iger*: Uporablja se za ustvarjanje orodij za razvoj iger, kot so urejevalniki nivojev, debuggerji in druga orodja, ki zahtevajo interaktivni uporabniški vmesnik.
- *Interaktivna orodja*: Uporablja se za razvoj različnih interaktivnih orodij, kot so simulacije, vizualizacije podatkov in druge aplikacije, ki zahtevajo uporabniški vmesnik.
- *Prototipiranje*: Zaradi svoje enostavnosti in hitrosti je ImGui priljubljen za prototipiranje uporabniških vmesnikov.
ImGui je zaradi svoje preprostosti, učinkovitosti in fleksibilnosti priljubljena izbira za razvijalce, ki potrebujejo hitro in enostavno rešitev za ustvarjanje uporabniških vmesnikov v svojih aplikacijah
#pagebreak()
== CMAKE
*CMake* je orodje za avtomatizacijo gradnje (build automation tool), ki je zasnovano za upravljanje procesa prevajanja, sestavljanja in paketiranja programske kode zlasti v večplatformskih okoljih. Razvil ga je Kitware leta 2000, da bi olajšal proces gradnje programov, ki so napisani v različnih programskih jezikih, kot so C, C++, Fortran in drugi @web_cmake @wiki_cmake.
=== Glavne značilnosti CMake:
1. *Večplatformska podpora:* CMake omogoča generiranje gradbenih datotek (npr. Makefile za Unix/Linux, projektne datoteke za Visual Studio na Windowsu, Xcode na macOS) za različne operacijske sisteme in razvojna okolja. To pomeni, da lahko isto izvorno kodo prevajamo in sestavljamo na različnih platformah brez spreminjanja gradbenih skript.
2. *Konfiguracijske datoteke (CMakeLists.txt):* CMake uporablja tekstovne datoteke z imenom `CMakeLists.txt`, ki vsebujejo navodila za konfiguracijo projekta. Te datoteke opisujejo, kako naj se projekt sestavi: kateri viri (source files) so vključeni, katere knjižnice so potrebne in kakšne so odvisnosti med posameznimi komponentami.
3. *Upravljanje odvisnosti:* CMake omogoča enostavno upravljanje odvisnosti med knjižnicami in programi. Podpira iskanje in povezovanje z zunanjimi knjižnicami (npr. OpenCV, Boost) in omogoča definicijo lastnih ciljev (targets), kot so izvršljivi programi ali knjižnice.
4. *Podpora za različne prevajalnike:* CMake deluje z različnimi prevajalniki (npr. GCC, Clang, MSVC) in omogoča uporabo specifičnih nastavitev za vsakega od njih.
5. *Modularnost in ponovna uporaba kode:* CMake podpira modularno strukturo projektov, kar olajša ponovno uporabo kode in deljenje knjižnic med različnimi projekti.
=== Kako CMake deluje?
1. *Konfiguracija:* CMake prebere datoteke `CMakeLists.txt` in generira gradbene datoteke za izbrani sistem (npr. Makefile ali Visual Studio projekt).
2. *Gradnja:* Uporabnik nato uporabi ustrezno gradbeno orodje (npr. `make`, Visual Studio) za prevajanje in sestavljanje projekta.
3. *Namestitev (opcijsko):* CMake lahko generira tudi namestitvene skripte za namestitev programa ali knjižnice v sistem.
=== Tipična uporaba CMake:
- *Razvoj programske opreme v C/C++:* CMake je standardno orodje za gradnjo projektov v C in C++ zlasti v večjih ali kompleksnih projektih.
- *Večplatformski projekti:* Če razvijamo programsko opremo, ki mora delovati na več platformah (Windows, Linux, macOS), je CMake idealno orodje za upravljanje gradnje.
- *Integracija z drugimi orodji:* CMake se lahko integrira z orodji za testiranje (npr. CTest), paketiranje (npr. CPack) in dokumentacijo.
=== Primer preproste datoteke `CMakeLists.txt`:
```Cmake
cmake_minimum_required(VERSION 3.10)
project(MojProjekt)
add_executable(moj_program main.cpp)
```
Ta datoteka določa, da je minimalna potrebna različica CMake 3.10, da je ime projekta "MojProjekt" in da naj se iz datoteke `main.cpp` generira izvršljivi program z imenom `moj_program`.
CMake je močno orodje, ki olajša in avtomatizira proces gradnje programske opreme zlasti v večplatformskih okoljih. Njegova glavna prednost je fleksibilnost in možnost generiranja gradbenih datotek za različne sisteme, kar ga naredi nezaobhodljivega pri razvoju v C in C++.
#pagebreak()
== SQLITE3
*SQLite* je lahka, samostoječa in vdelana relacijska baza podatkov, ki deluje neposredno v programu, brez potrebe po ločenem strežniku. Razvil jo je D. Richard Hipp leta 2000 z namenom, da ponudi preprosto, hitro in zanesljivo rešitev za upravljanje podatkov, ki ne zahteva nastavljanja ali upravljanja strežnika. SQLite je napisana v jeziku C in je na voljo kot knjižnica, kar pomeni, da se lahko integrira neposredno v aplikacije @web_sqlite @wiki_sqlite.
=== Glavne značilnosti SQLite:
1. *Samostoječa in vdelana:* SQLite deluje kot del programa in ne zahteva ločenega procesa ali strežnika. To pomeni, da aplikacija komunicira neposredno s podatkovno bazo, kar zmanjša kompleksnost in poveča hitrost delovanja.
2. *Enostavnost in prenosljivost:* Celotna baza podatkov je shranjena v eni datoteki, kar olajša prenos in upravljanje. Datoteko lahko preprosto kopiramo med različnimi sistemi, ne da bi potrebovali dodatne konfiguracije.
3. *Nizke zahteve:* SQLite ne potrebuje nastavljanja ali upravljanja, kar jo naredi idealno za uporabo v mobilnih napravah, vgrajenih sistemih in manjših aplikacijah, kjer so viri omejeni.
4. *Podpora za SQL:* SQLite podpira večino standardnih SQL ukazov (npr. `SELECT`, `INSERT`, `UPDATE`, `DELETE`), kar omogoča enostavno upravljanje podatkov z uporabo poznanega jezika.
5. *Zanesljivost in varnost:* SQLite zagotavlja transakcijsko varnost (ACID), kar pomeni, da so podatki vedno dosledni, tudi v primeru napak ali prekinitve dela.
6. *Odprtokodna in brezplačna:* SQLite je odprtokodna in brezplačna, kar pomeni, da jo lahko prostovoljno uporabljamo v komercialnih in nekomercialnih projektih.
=== Uporaba SQLite:
- *Mobilne aplikacije:* SQLite je priljubljena izbira za shranjevanje podatkov v mobilnih aplikacijah (npr. Android, iOS), saj ne potrebuje strežnika in deluje lokalno na napravi.
- *Vgrajeni sistemi:* Zaradi svoje preprostosti in nizkih zahtev je SQLite pogosto uporabljena v vgrajenih sistemih, kot so pametne ure, senzorji in druge naprave z omejenimi viri.
- *Spletne aplikacije:* Uporablja se za lokalno shranjevanje podatkov v brskalnikih (npr. Web SQL API) ali kot preprosta rešitev za manjšo spletno stran.
- *Testiranje in razvoj:* SQLite je pogosto uporabljena med razvojem in testiranjem, saj omogoča hitro in enostavno ustvarjanje in upravljanje podatkovnih baz.
- *Aplikacije za namizje:* Številne namizne aplikacije uporabljajo SQLite za lokalno shranjevanje podatkov, saj ne zahteva dodatne infrastrukture.
SQLite je torej idealna rešitev, če potrebujemo preprosto, hitro in zanesljivo podatkovno bazo, ki deluje neposredno v naši aplikaciji. Je ena najpogosteje uporabljanih podatkovnih baz na svetu predvsem zaradi svoje preprostosti in učinkovitosti.
#pagebreak()
= STRUKTURA APLIKACIJE = STRUKTURA APLIKACIJE
Preden opišemo posamezne dele, na kratko povzemimo, kaj se v aplikaciji zgodi med eno generacijo. Stanje aplikacije je generacija 16 genomov. Za vsak genom vizualizator izriše sliko, jedro jo prikaže uporabniku in sprejme njegovo oceno, DNA menedžer pa oceno zabeleži. Ko je ocenjenih vseh 16 osebkov, menedžer iz všečnih osebkov zgradi novo generacijo genomov, jedro izračuna podobnost med njimi in postopek se ponovi.
Temu ustreza tudi razdelitev besedila: uporabniški vmesnik in ocenjevanje sta opisana v @rotacija_slike, hramba stanja generacije v @dna_menedzer, zapis genoma v @zapis_dna, tvorjenje nove generacije v @genetski_algoritem, izris slike iz genoma pa v @dna_vizualizacija.
Aplikacija ima tri glavne enote: DNA menedžer, DNA vizualizator in jedro. DNA menedžer hrani generacijo genomov, skrbi za vrstni red njihovega prikaza in za tvorjenje nove generacije, DNA vizualizator iz genoma izriše sliko, v jedru pa so preostale komponente, med njimi omrežni vmesnik in vmesnik za shranjevanje. Aplikacija ima tri glavne enote: DNA menedžer, DNA vizualizator in jedro. DNA menedžer hrani generacijo genomov, skrbi za vrstni red njihovega prikaza in za tvorjenje nove generacije, DNA vizualizator iz genoma izriše sliko, v jedru pa so preostale komponente, med njimi omrežni vmesnik in vmesnik za shranjevanje.
Jedro je hrbtenica, ki povezuje vse komponente. Ob zagonu preračuna razmerja za prikaz besedila in slik dreves, med uporabo pa skrbi za premik in rotacijo slike, ko uporabnik s prstom povleče po zaslonu. Ko poteg doseže rob zaslona, jedro oceno posreduje DNA menedžerju in od njega dobi naslednji genom za prikaz, ga preda vizualizatorju in vrnjeno sliko prikaže uporabniku. Vsaka ocena se sproti shrani na disk, ob koncu generacije pa se shrani celotna generacija in se pošlje na strežnik. Jedro je hrbtenica, ki povezuje vse komponente. Ob zagonu preračuna razmerja za prikaz besedila in slik dreves, med uporabo pa skrbi za premik in rotacijo slike, ko uporabnik s prstom povleče po zaslonu. Ko poteg doseže rob zaslona, jedro oceno posreduje DNA menedžerju in od njega dobi naslednji genom za prikaz, ga preda vizualizatorju in vrnjeno sliko prikaže uporabniku. Vsaka ocena se sproti shrani na disk, ob koncu generacije pa se shrani celotna generacija in se pošlje na strežnik.
@@ -402,7 +315,7 @@ Jedro je hrbtenica, ki povezuje vse komponente. Ob zagonu preračuna razmerja za
#pagebreak() #pagebreak()
== UPORABNIŠKI VMESNIK ROTACIJA SLIKE == UPORABNIŠKI VMESNIK ROTACIJA SLIKE <rotacija_slike>
Proces rotiranja slike glede na premik miške je sestavljen iz treh korakov Proces rotiranja slike glede na premik miške je sestavljen iz treh korakov
in uporabe trigometrije. in uporabe trigometrije.
@@ -461,9 +374,9 @@ V @mat_rotacija za $Delta"mouseV"$ lahko vidimo na (@Prikaz_razdalje), da ko je
#v(10pt) #v(10pt)
$ $
"newAngleOfVectorMI" & = "rotationRad" + "oldAngleOfVectorM" \ "newAngleOfVectorMI" & = "rotationRad" + "oldAngleOfVectorMI" \
"vectorMI"_x & = "sizeOfVectorMI" * cos("newAngleOfVectorMI") \ "vectorMI"_x & = "sizeOfVectorMI" * sin("newAngleOfVectorMI") \
"vectorMI"_y & = "sizeOfVectorMI" * sin("newAngleOfVectorMI") \ "vectorMI"_y & = "sizeOfVectorMI" * cos("newAngleOfVectorMI") \
"imageV"_x & = "vectorMI"_x + "mouseV"_x \ "imageV"_x & = "vectorMI"_x + "mouseV"_x \
"imageV"_y & = "vectorMI"_y + "mouseV"_y \ "imageV"_y & = "vectorMI"_y + "mouseV"_y \
"imageRotation" & = 90 + "rotation" "imageRotation" & = 90 + "rotation"
@@ -487,12 +400,11 @@ $
- $"orgImageV"$ originalna pozicija slike - $"orgImageV"$ originalna pozicija slike
=== Uporabljene funkcije === Uporabljene funkcije
V zgornjih enačbah nastopata dve standardni funkciji. Funkcija $"atan2"$ vrne kot vektorja v pravem kvadrantu, pri čemer upošteva predznaka obeh koordinat @wiki_atan2. Argumenta ji podajamo v vrstnem redu $(Delta x, Delta y)$, zato je dobljeni kot merjen od navpične osi in v enačbah za razstavljanje vektorja nastopa sinus pri komponenti $x$ in kosinus pri komponenti $y$. Funkcija $"lerp"(v_0, v_1, t) = v_0 + t (v_1 - v_0)$ linearno interpolira med vrednostma $v_0$ in $v_1$ s parametrom $t in [0, 1]$ @wiki_lerp. V zgornjih enačbah nastopata dve standardni funkciji. Funkcija $"atan2"$ vrne kot vektorja v pravem kvadrantu, pri čemer upošteva predznaka obeh koordinat @wiki_atan2. Argumenta ji podajamo v vrstnem redu $(Delta x, Delta y)$, zato je dobljeni kot merjen od navpične osi in v enačbah za razstavljanje vektorja nastopa sinus pri komponenti $x$ in kosinus pri komponenti $y$. Funkcija $"lerp"(v_0, v_1, t) = v_0 + t (v_1 - v_0)$ linearno interpolira med vrednostma $v_0$ in $v_1$ s parametrom $t in [0, 1]$ @wiki_lerp.
#pagebreak() #pagebreak()
== DNA MANAGER == DNA MENEDŽER <dna_menedzer>
#let next = `next` #let next = `next`
#let like = `like` #let like = `like`
#let newGen = `newGen` #let newGen = `newGen`
@@ -501,8 +413,8 @@ DNA menedžer hrani celotno populacijo tekoče generacije, skrbi za vrstni red p
Menedžer dovoli, da naslednji osebek pridobimo, še preden ocenimo prejšnjega. Aplikacija to izkorišča tako, da hkrati hrani dva osebka: zgornjega, ki ga uporabnik premika in ocenjuje, in spodnjega, ki je že izrisan in čaka v ozadju. Po vsaki oceni spodnji osebek postane zgornji, na njegovo mesto pa vstopi naslednji. Menedžer dovoli, da naslednji osebek pridobimo, še preden ocenimo prejšnjega. Aplikacija to izkorišča tako, da hkrati hrani dva osebka: zgornjega, ki ga uporabnik premika in ocenjuje, in spodnjega, ki je že izrisan in čaka v ozadju. Po vsaki oceni spodnji osebek postane zgornji, na njegovo mesto pa vstopi naslednji.
Za sledenje stanju generacije menedžer uporablja dva indeksa v vektorju osebkov. Indeks `queued` kaže na naslednji osebek za prikaz, indeks `showed` pa na naslednji osebek za oceno. Funkcija #next vrne osebek na mestu `queued` in indeks poveča; ko `queued` doseže velikost generacije, vrne `nullptr`. Funkcija #like uvrsti indeks ocenjenega osebka med všečne ali nevšečne in poveča `showed`; ko sta oba indeksa na koncu vektorja, vrne `false`. Glavna logika evolucije je v funkciji #newGen in je opisana v Za sledenje stanju generacije menedžer uporablja dva indeksa v vektorju osebkov. Indeks `queued` kaže na naslednji osebek za prikaz, indeks `showed` pa na naslednji osebek za oceno. Funkcija #next vrne osebek na mestu `queued` in indeks poveča; ko `queued` doseže velikost generacije, vrne `nullptr`. Funkcija #like uvrsti indeks ocenjenega osebka med všečne ali nevšečne in poveča `showed`; ko sta oba indeksa na koncu vektorja, vrne `false`. Glavna logika evolucije je v funkciji #newGen in je opisana v @genetski_algoritem.
// @genetski_algoritem.
#figure(caption: [DnaManager])[ #figure(caption: [DnaManager])[
#grid( #grid(
columns: 2, columns: 2,
@@ -551,7 +463,7 @@ Za sledenje stanju generacije menedžer uporablja dva indeksa v vektorju osebkov
#pagebreak() #pagebreak()
== ZAPIS DNA == ZAPIS DNA <zapis_dna>
Dna je sestavljen iz dveh struktur (@dna_objekt). Zanimliv del, ki ga opazimo, je, da je vsak gen točno en bite dolg in celoten objekt je v pomnilniku samo en velik list bitov (array of bites). To značinost uporabljamo v complementarnih funkcijah `newDna`, `makeChiled`, `clone` in `mutate`. Dna je sestavljen iz dveh struktur (@dna_objekt). Zanimliv del, ki ga opazimo, je, da je vsak gen točno en bite dolg in celoten objekt je v pomnilniku samo en velik list bitov (array of bites). To značinost uporabljamo v complementarnih funkcijah `newDna`, `makeChiled`, `clone` in `mutate`.
#figure(caption: [DNA objekt])[ #figure(caption: [DNA objekt])[
@@ -634,11 +546,78 @@ Ostale funkcije so podobne; najprej pretvorimo v `uint_8*` in nato:
- `clone` naključno kopiramo iz starša in vstavljamo naključne vrednosti, - `clone` naključno kopiramo iz starša in vstavljamo naključne vrednosti,
- `mutate` pa sprecificirano število vrednosti spremenimo v naključne vrednosti. - `mutate` pa sprecificirano število vrednosti spremenimo v naključne vrednosti.
#pagebreak()
== GENETSKI ALGORITEM <genetski_algoritem>
Ob koncu vsake generacije funkcija `newGen` iz uporabnikovih ocen zgradi novo populacijo. Ker prav ta korak določa, kako hitro se populacija zoži k uporabnikovemu okusu in koliko naključnosti v njej ostane, ga v nadaljevanju opišemo po posameznih delih.
=== Selekcija in elitizem
Vsi všečni osebki se v naslednjo generacijo prenesejo nespremenjeni, otroci pa prepišejo samo mesta nevšečnih osebkov. Velikost populacije zato ostane enaka, delež na novo ustvarjenih osebkov pa je enak deležu nevšečnih ocen v prejšnji generaciji. Če uporabnik označi kot všečnih deset od šestnajstih osebkov, se na novo ustvari le preostalih šest.
=== Križanje
Za vsako mesto nevšečnega osebka enakomerno naključno izberemo dva različna starša iz množice všečnih. Križanje je uniformno na ravni bajtov: za vsak bajt genoma z verjetnostjo 0,5 prepišemo vrednost prvega ali drugega starša, tako da otrok v povprečju podeduje polovico genoma od vsakega starša.
=== Robna primera
- *En všečni osebek.* Križanje ni mogoče, zato uporabimo funkcijo `clone`, ki vsak bajt otroka z verjetnostjo 0,5 prepiše od starša, sicer pa ga nadomesti z naključno vrednostjo. Polovica genoma otroka je torej naključna, kar je najmočnejši vir naključnosti v celotnem postopku.
- *Noben všečni osebek.* Celotno populacijo nadomestimo z novo naključno populacijo.
=== Mutacija
Po križanju mutiramo *vse* osebke, tudi všečne. Mutacija na naključno izbranih mestih zamenja tri bajte z naključnimi vrednostmi. Genom obsega 180 bajtov, kar pomeni približno 1,7 % genoma na osebek na generacijo.
Ker mutacija deluje tudi na všečne osebke, se populacija ne more nikoli povsem poenotiti. To je tudi razlog, da podobnost znotraj generacije doseže strop pri približno 82 % in ne pri 100 % (@interpretacija_konvergence), kar so kot motečo lastnost opazili tudi uporabniki (@moteca_podobnost).
#figure(caption: [Postopek tvorjenja nove generacije])[
```
newGen(populacija, všečni, nevšečni):
če je množica všečni prazna:
za vsak osebek v populaciji: newDna(osebek)
končaj
če ima množica všečni natanko en element:
za vsako mesto i iz množice nevšečni:
clone(všečni[0], populacija[i])
sicer:
za vsako mesto i iz množice nevšečni:
p1, p2 = dva različna naključna starša iz množice všečni
makeChild(p1, p2, populacija[i])
za vsak osebek v populaciji: mutate(osebek, 3)
```
]<psevdokoda_newgen>
=== Viri naključnosti
V postopku nastopa pet virov naključnosti (@tabela_nakljucnost). Nobeden od njih ni odvisen od zaporedne številke generacije: verjetnost križanja in stopnja mutacije sta skozi celotno sejo konstantni. Naključnost se spreminja le posredno, prek števila všečkov, saj to določi, katera veja postopka se izvede in koliko osebkov se sploh na novo ustvari. Kako se to pozna na izmerjeni podobnosti med osebki, prikazuje @interpretacija_konvergence.
#align(center)[
#figure(caption: [Viri naključnosti v postopku tvorjenja nove generacije])[
#table(
columns: 3,
align: (left, left, left),
table.header([Vir], [Obseg naključnosti], [Odvisnost od generacije]),
table.hline(),
[Izbira staršev], [enakomerno iz množice všečnih], [posredna, prek števila všečkov],
[Uniformno križanje], [$p = 0,5$ na bajt], [ni odvisno],
[Kloniranje (en všeček)], [$p = 0,5$ naključni bajt, tj. približno polovica genoma], [le v tem robnem primeru],
[Mutacija], [3 bajti od 180, tj. približno 1,7 % genoma], [ni odvisno],
[Nova naključna populacija \ (brez všečkov)], [celoten genom vseh osebkov], [le v tem robnem primeru],
)
]<tabela_nakljucnost>
]
#pagebreak() #pagebreak()
== DNA VIZUALIZACIJA === Izbira parametrov <izbira_parametrov>
Postopek ima dva prosta parametra: velikost generacije in stopnjo mutacije. K tema se pridruži še dolžina seje, torej število generacij, ki jih uporabnik prehodi.
*Velikost generacije* je nastavljena na 16 osebkov. Vrednost je kompromis med dvema nasprotujočima si zahtevama. Večja generacija pomeni več staršev in s tem zanesljivejšo selekcijo, hkrati pa uporabnik dlje čaka na spremembo, saj mora pred vsako novo generacijo opraviti toliko ocen, kolikor je osebkov. Pri 16 osebkih uporabnik vidi učinek svojih ocen po 16 potezah. Vrednosti nismo določili eksperimentalno, zato je ne predstavljamo kot optimalne; testni uporabniki so opozorili prav na nasprotni učinek, da je 16 osebkov premalo za zanesljivo razlikovanje med lastnostmi slik (@nepricakovani_rezultati).
*Stopnja mutacije* je nastavljena na tri bajte na osebek. Pri manjši vrednosti populacija hitreje obtiči v enolični množici zelo podobnih dreves, pri večji pa uporabnik med generacijami ne prepozna več svojih izbir. Tudi ta vrednost je bila izbrana med razvojem po opazovanju izrisanih generacij in ne z meritvijo.
*Dolžina seje* ni omejena; aplikacija lahko teče poljubno dolgo. Seje, ki jih analiziramo v @poglavje_podobnost, so bile prekinjene po dvanajsti generaciji, ker podobnost znotraj generacije do takrat naraste s približno 0 % na 82 % in nato ne narašča več (@graf_podobnost). Nadaljnje generacije torej ne prinesejo več opazne konvergence, prinesejo pa dodatnih 16 potez na generacijo.
Sistematično iskanje najboljših vrednosti teh treh parametrov presega obseg naloge in ga navajamo med predlogi za nadaljnje delo (@zakljucek).
#pagebreak()
== DNA VIZUALIZACIJA <dna_vizualizacija>
#figure( #figure(
@@ -710,6 +689,7 @@ Gore so bile med vsemi elementi najzahtevnejše. Rešili smo jih s trakom trikot
Barvno shemo ozadja določata dva gena: `colorSet` izbere eno od štirih shem, tri dnevne in eno nočno, `moonY` pa znotraj dnevne sheme enega od štirih odtenkov, kar ustreza različnim delom dneva. Skupaj je torej na voljo 13 barvnih shem, znotraj vsake pa se zvezno spreminjajo oblika gorovja, razporeditev zvezd ter položaj in velikost sonca oziroma lune. Barvno shemo ozadja določata dva gena: `colorSet` izbere eno od štirih shem, tri dnevne in eno nočno, `moonY` pa znotraj dnevne sheme enega od štirih odtenkov, kar ustreza različnim delom dneva. Skupaj je torej na voljo 13 barvnih shem, znotraj vsake pa se zvezno spreminjajo oblika gorovja, razporeditev zvezd ter položaj in velikost sonca oziroma lune.
#block(breakable: false)[ #block(breakable: false)[
#grid( #grid(
columns: 3, columns: 3,
@@ -812,7 +792,7 @@ Veja je struktura, ki vsebuje podatke o:
==== Algoritem ==== Algoritem
Algoritem drevo gradi po vejah in ne rekurzivno. V seznamu hrani opise vej, ki jih je še treba izrisati; iz seznama vzame prvi opis, izriše vejo in na njen konec doda opise njenih otrok, dokler seznam ni prazen. Vsaka veja pri tem prevzame nekaj lastnosti od starša (končno debelino in barvo), preostale pa prebere iz gena za svojo globino. Tako je celotno drevo določeno z osmimi geni, po enim za vsako globino, in s tem, kar veje podedujejo od svojih staršev. Algoritem drevo gradi po vejah in ne rekurzivno. V seznamu hrani opise vej, ki jih je še treba izrisati; iz seznama vzame prvi opis, izriše vejo in na njen konec doda opise njenih otrok, dokler seznam ni prazen. Vsaka veja pri tem prevzame nekaj lastnosti od starša (končno debelino in barvo), preostale pa prebere iz gena za svojo globino. Tako je celotno drevo določeno z osmimi geni, po enim za vsako globino, in s tem, kar veje podedujejo od svojih staršev.
Začnemo tako, da v seznam damo opis debla, ki ga prikazuje @prvi_drawArgs, in ponavljamo naslednje korake, dokler imamo nekaj v seznamu. Začnemo tako, da v seznam damo opis debla, ki ga prikazuje @prvi_drawArgs, in ponavljamo naslednje korake, dokler je v seznamu še kaj.
#figure(caption: [Prvi DrawArgs])[ #figure(caption: [Prvi DrawArgs])[
```cpp ```cpp
struct DrawArgs struct DrawArgs
@@ -984,7 +964,7 @@ Začnemo tako, da v seznam damo opis debla, ki ga prikazuje @prvi_drawArgs, in p
== OPTIMIZACIJE == OPTIMIZACIJE
=== Optimizacija izrisa === Optimizacija izrisa <optimizacija_izrisa>
Sprva se je drevo izrisalo s pomočjo raylib funkcije `DrawLineEx`, drevo se je izrisovalo vsak okvir (frame). Kasneje smo hoteli dodati veje s spremenljivo debelino, najlažja rešitev, ki smo se jo spomnili, je bila, da narišemo krogce s spremenljivim polmerom. To nas je prisililo, da moramo narisati čez 55000 krogov. Z raylibovo funkcijo `DrawCircleV` za risanje krogov je applikacija začela zaostajati (lagged). To smo rešili tako, da najprej drevo narišemo na teksturo, ki je zelo enostavna za izris. To je rešilo večino problemov, občasno se je še vedno pojavil zaostanek (lag spike), ki smo ga rešili tako, da smo omejili število risalnih klicev na okvir in drevo narisali v par okvirjih. Sprva se je drevo izrisalo s pomočjo raylib funkcije `DrawLineEx`, drevo se je izrisovalo vsak okvir (frame). Kasneje smo hoteli dodati veje s spremenljivo debelino, najlažja rešitev, ki smo se jo spomnili, je bila, da narišemo krogce s spremenljivim polmerom. To nas je prisililo, da moramo narisati čez 55000 krogov. Z raylibovo funkcijo `DrawCircleV` za risanje krogov je applikacija začela zaostajati (lagged). To smo rešili tako, da najprej drevo narišemo na teksturo, ki je zelo enostavna za izris. To je rešilo večino problemov, občasno se je še vedno pojavil zaostanek (lag spike), ki smo ga rešili tako, da smo omejili število risalnih klicev na okvir in drevo narisali v par okvirjih.
@@ -1011,6 +991,7 @@ Sprva se je drevo izrisalo s pomočjo raylib funkcije `DrawLineEx`, drevo se je
Na začetku (@dna_manager_v1) je bil DnaManager struktura kazalcev na `Dna`, kar pomeni, da stanja ni bilo mogoče shraniti v datoteko, saj kazalci po ponovnem zagonu niso več veljavni. Zato smo kazalce zamenjali z indeksi (@dna_manager_v2), ki jih je mogoče zapisati; ob tem se je izkazalo še, da je za predpomnilnik ugodno, če so indeksi v seznamu `queued` urejeni naraščajoče. Na začetku (@dna_manager_v1) je bil DnaManager struktura kazalcev na `Dna`, kar pomeni, da stanja ni bilo mogoče shraniti v datoteko, saj kazalci po ponovnem zagonu niso več veljavni. Zato smo kazalce zamenjali z indeksi (@dna_manager_v2), ki jih je mogoče zapisati; ob tem se je izkazalo še, da je za predpomnilnik ugodno, če so indeksi v seznamu `queued` urejeni naraščajoče.
Med razhroščevanjem smo opazili, da sta seznama `queued` in `showed` odveč. Ker osebke prikazujemo in ocenjujemo v enakem vrstnem redu, kot so shranjeni v vektorju, oba seznama vedno vsebujeta strnjen del vektorja, zato ju je mogoče nadomestiti z dvema premikajočima se indeksoma v vektorju osebkov (@dna_manager_v3): pred indeksom `showed` so ocenjeni osebki, med `showed` in `queued` so prikazani in še neocenjeni, za `queued` pa še neprikazani. Med razhroščevanjem smo opazili, da sta seznama `queued` in `showed` odveč. Ker osebke prikazujemo in ocenjujemo v enakem vrstnem redu, kot so shranjeni v vektorju, oba seznama vedno vsebujeta strnjen del vektorja, zato ju je mogoče nadomestiti z dvema premikajočima se indeksoma v vektorju osebkov (@dna_manager_v3): pred indeksom `showed` so ocenjeni osebki, med `showed` in `queued` so prikazani in še neocenjeni, za `queued` pa še neprikazani.
#align(center)[ #align(center)[
#grid( #grid(
columns: 2, columns: 2,
@@ -1064,7 +1045,7 @@ Med razhroščevanjem smo opazili, da sta seznama `queued` in `showed` odveč. K
#pagebreak() #pagebreak()
= PODOBNOST = PODOBNOST <poglavje_podobnost>
Med sejo želimo uporabniku pokazati, kako daleč je evolucija napredovala, hkrati pa potrebujemo mero, s katero lahko delovanje genetskega postopka spremljamo tudi pri razvoju. Za oboje uporabljamo isto količino: *povprečno podobnost med vsemi pari osebkov v trenutni generaciji*. Pri 16 osebkih je to povprečje čez $binom(16, 2) = 120$ parov. Med sejo želimo uporabniku pokazati, kako daleč je evolucija napredovala, hkrati pa potrebujemo mero, s katero lahko delovanje genetskega postopka spremljamo tudi pri razvoju. Za oboje uporabljamo isto količino: *povprečno podobnost med vsemi pari osebkov v trenutni generaciji*. Pri 16 osebkih je to povprečje čez $binom(16, 2) = 120$ parov.
Poudariti je treba, kaj ta količina pove in kaj ne. Razdalja med genoma pove, kako podobni sta si sliki, torej kako zelo se je populacija zožila oziroma kam je izbira skonvergirala. Ne pove pa, ali so slike uporabniku dejansko všeč: populacija lahko skonvergira tudi k drevesom, s katerimi uporabnik ni zadovoljen, na primer kadar med prikazanimi osebki ni bilo nobenega, ki bi mu ustrezal. Podobnost je torej *mera konvergence*, ne mera všečnosti. V uporabniškem vmesniku je prikazana kot odstotek "všečnosti", kar je zaradi igrifikacije razumljivo, vsebinsko pa neposrečeno; primernejša oznaka bi bila "ujemanje z vašim izborom" ali "stopnja konvergence". Poudariti je treba, kaj ta količina pove in kaj ne. Razdalja med genoma pove, kako podobni sta si sliki, torej kako zelo se je populacija zožila oziroma kam je izbira skonvergirala. Ne pove pa, ali so slike uporabniku dejansko všeč: populacija lahko skonvergira tudi k drevesom, s katerimi uporabnik ni zadovoljen, na primer kadar med prikazanimi osebki ni bilo nobenega, ki bi mu ustrezal. Podobnost je torej *mera konvergence*, ne mera všečnosti. V uporabniškem vmesniku je prikazana kot odstotek "všečnosti", kar je zaradi igrifikacije razumljivo, vsebinsko pa neposrečeno; primernejša oznaka bi bila "ujemanje z vašim izborom" ali "stopnja konvergence".
@@ -1207,6 +1188,7 @@ Algoritmi so bili testirani na računalniku z AMD Ryzen 5 5500U procesorjem in 1
) )
]<graf_podobnost> ]<graf_podobnost>
] ]
#pagebreak() #pagebreak()
=== Čas računanja podobnosti v μs === Čas računanja podobnosti v μs
#align(center)[ #align(center)[
@@ -1264,10 +1246,10 @@ Algoritmi so bili testirani na računalniku z AMD Ryzen 5 5500U procesorjem in 1
lq.plot(ts, tcs, mark: none, label: [Kosinusna]), lq.plot(ts, tcs, mark: none, label: [Kosinusna]),
lq.plot(ts, tcsv, mark: none, label: [Kosinusna variacija]), lq.plot(ts, tcsv, mark: none, label: [Kosinusna variacija]),
lq.plot(ts, th, mark: none, label: [Hammingova]), lq.plot(ts, th, mark: none, label: [Hammingova]),
lq.plot(ts, tl, mark: none, label: [Levenshteinova]),
) )
]<graf_cas> ]<graf_cas>
] ]
#pagebreak() #pagebreak()
== Interpretacija konvergence <interpretacija_konvergence> == Interpretacija konvergence <interpretacija_konvergence>
Krivulje na @graf_podobnost se med seboj razlikujejo po zalogi vrednosti, njihova oblika pa je enaka, saj vse merijo isti pojav: kako zelo se je populacija zožila. Če jih beremo skupaj s številom všečkov po generacijah (@tabela_vsecki_gen), postane razvidno, da oblike krivulje ne določa zaporedna številka generacije, temveč uporabnikove ocene, saj te izberejo vejo postopka tvorjenja nove generacije. Krivulje na @graf_podobnost se med seboj razlikujejo po zalogi vrednosti, njihova oblika pa je enaka, saj vse merijo isti pojav: kako zelo se je populacija zožila. Če jih beremo skupaj s številom všečkov po generacijah (@tabela_vsecki_gen), postane razvidno, da oblike krivulje ne določa zaporedna številka generacije, temveč uporabnikove ocene, saj te izberejo vejo postopka tvorjenja nove generacije.
@@ -1282,6 +1264,7 @@ Krivulje na @graf_podobnost se med seboj razlikujejo po zalogi vrednosti, njihov
Iz tega je razvidno, da je mera razdalje uporabna tudi kot diagnostika postopka in ne le kot številka na zaslonu; iz njenega poteka je mogoče razbrati, katera veja postopka se je izvedla in kako močno je vplivala na populacijo. Naravno nadaljevanje tega opazovanja je merjenje hitrosti konvergence pri različnih stopnjah mutacije in različnih velikostih generacije, kar navajamo med predlogi za nadaljnje delo (@zakljucek). Iz tega je razvidno, da je mera razdalje uporabna tudi kot diagnostika postopka in ne le kot številka na zaslonu; iz njenega poteka je mogoče razbrati, katera veja postopka se je izvedla in kako močno je vplivala na populacijo. Naravno nadaljevanje tega opazovanja je merjenje hitrosti konvergence pri različnih stopnjah mutacije in različnih velikostih generacije, kar navajamo med predlogi za nadaljnje delo (@zakljucek).
#pagebreak()
== Odločitev <odlocitev_mera> == Odločitev <odlocitev_mera>
Izbrali smo Hammingovo razdaljo. Ustreza vsem štirim kriterijem iz uvoda tega poglavja: računa se neposredno nad bajti genoma, njena zaloga vrednosti je celoten razpon od 0 % do 100 %, med konvergenco narašča, izračun 120 parov pa traja približno 100 μs. Odločilna je bila zaloga vrednosti: pri naključni začetni populaciji se začne pri približno 0 % in se z zoževanjem izbire pomika proti 100 %, zato je uporabniku razumljiva brez dodatne razlage. Kosinusna podobnost je za prikaz manj primerna, ker se že pri naključni populaciji začne pri 75 %, evklidska pri 59 %, skalarni produkt pa se giblje le med 25 % in 32 %. Časov računanja pri odločitvi nismo upoštevali, saj so vse mere razen Levenshteinove pod 0,5 ms, kar je za enkraten izračun ob prehodu med generacijama zanemarljivo; Levenshteinova razdalja je s približno 100 ms na generacijo za prikaz v realnem času neuporabna. Izbrali smo Hammingovo razdaljo. Ustreza vsem štirim kriterijem iz uvoda tega poglavja: računa se neposredno nad bajti genoma, njena zaloga vrednosti je celoten razpon od 0 % do 100 %, med konvergenco narašča, izračun 120 parov pa traja približno 100 μs. Odločilna je bila zaloga vrednosti: pri naključni začetni populaciji se začne pri približno 0 % in se z zoževanjem izbire pomika proti 100 %, zato je uporabniku razumljiva brez dodatne razlage. Kosinusna podobnost je za prikaz manj primerna, ker se že pri naključni populaciji začne pri 75 %, evklidska pri 59 %, skalarni produkt pa se giblje le med 25 % in 32 %. Časov računanja pri odločitvi nismo upoštevali, saj so vse mere razen Levenshteinove pod 0,5 ms, kar je za enkraten izračun ob prehodu med generacijama zanemarljivo; Levenshteinova razdalja je s približno 100 ms na generacijo za prikaz v realnem času neuporabna.
@@ -1399,11 +1382,18 @@ Po tem, ko smo objavili aplikacijo na Play Store, smo prosili prijatelje in sode
== Ni intuitivno, kako uprabljati aplikacijo == Ni intuitivno, kako uprabljati aplikacijo
Uporabnikom ni bilo jasno, kako uporabiti aplikacijo, ni jim bilo, jasno da lahko povlečejo sliko levo ali desno, da označijo, ali jim je všeč ali ne. Da rešimo to, lahko ob prvem zagonu aplikacije prikažemo animacijo, kako lahko premikajo sliko po zaslonu. Uporabnikom ni bilo jasno, kako uporabiti aplikacijo, ni jim bilo, jasno da lahko povlečejo sliko levo ali desno, da označijo, ali jim je všeč ali ne. Da rešimo to, lahko ob prvem zagonu aplikacije prikažemo animacijo, kako lahko premikajo sliko po zaslonu.
== Nepričakovani rezultati == Nepričakovani rezultati <nepricakovani_rezultati>
Po razlagi, kako uporabljati aplikacijo, jim ni bilo jasno, zakaj niso videli nobenih sprememb na drevesih in v rezultatu podobnosti. Tukaj je glavni problem majhno število vzorcev na generacijo (trenutno 16); s tem težko izločimo, kaj je uporabniku všeč. Med testiranjem nam večje število vzorcev ni bilo všeč, ker predolgo traja, da pridemo skozi generacijo in vidimo spremembe.Ker smo vedeli kako deluje algoritem, med izdelavo aplikacije te nismo uporabljali kot uporabnik. Namesto da bi izbirali videz celotne slike, smo izbirali specifične lastnosti na slikah. Nekaterim uporabnikom je to vspelo ugotoviti in so s tem ustvarili precej lepe slike. Po razlagi, kako uporabljati aplikacijo, jim ni bilo jasno, zakaj na drevesih in v prikazanem odstotku niso videli sprememb. Glavni razlog je majhno število osebkov na generacijo (16), saj iz 16 binarnih ocen težko izluščimo, katera lastnost slike je uporabniku všeč. Večje generacije med razvojem nismo uporabili, ker traja predolgo, da uporabnik pride skozi generacijo in vidi učinek svojih ocen; kompromis je opisan v @izbira_parametrov.
== Moteč rezultat podobnosti Pokazala se je še ena razlika. Ker smo poznali delovanje postopka, aplikacije med razvojem nismo uporabljali kot uporabnik: namesto celotnega videza slike smo ocenjevali posamezne lastnosti, na primer barvo ali gostoto vej. Nekateri uporabniki so to sami ugotovili in s takim načinom ocenjevanja prišli do vidno lepših dreves, drugi pa ne, kar kaže, da bi aplikacija ta način dela morala nakazati sama.
Nekaj uporabnikov nam je sporočilo da jih je motilo, da jim ni uspelo doseči 100 % podobnosti. Da lahko to rešimo, bi mogoče lahko uporabili kak drugačen algoritem ali pa bi lahko preslikali rezultat, da se vsaka vrednost nad 85 % prikaže kot 100 %.
== Moteč rezultat podobnosti <moteca_podobnost>
Nekaj uporabnikov je sporočilo, da jih je motilo, ker jim ni uspelo doseči 100 %. Vzrok je v postopku: mutacija se izvede tudi na všečnih osebkih, zato se populacija ne more povsem poenotiti in vrednost se ustali okoli 82 % (@interpretacija_konvergence). Moteč je torej tudi način prikaza, saj prikazana vrednost ni ocena všečnosti, ampak mera konvergence populacije, uporabnik pa jo razume kot cilj, ki ga je treba doseči. Rešitev je dvojna: količino poimenovati tako, kot deluje (na primer "ujemanje z vašim izborom"), in prikazano vrednost raztegniti čez celoten razpon do dejansko dosegljivega maksimuma.
== Vsiljena barvna paleta <vsiljena_paleta_ref>
Barve dreves in ozadja so del genoma in so v prvi generaciji naključne, zato uporabnik na začetku ne more vplivati na osnovni videz slik. Če mu prikazane barvne kombinacije ne ustrezajo, ocenjuje slike, med katerimi mu nobena ni všeč, in njegove ocene so za postopek šum in ne signal. Podatki to potrjujejo: v prvih dveh generacijah analizirane seje je bilo všečkanih le 4 oziroma 3 od 16 osebkov (@tabela_vsecki_gen), medtem ko jih je bilo v generacijah 3--6 med 11 in 16.
Razlog za tako zasnovo je bil, da naj evolucija zajame celotno sliko; če bi uporabnik barve izbral vnaprej, bi iz iskalnega prostora izpadli geni `colorR`, `colorG`, `colorB`, `colorVar` in `colorSet`, torej približno tretjina genoma, in vpliv evolucije na končni videz bi bil bistveno manjši. Primernejša rešitev, ki je nismo izvedli, je uvodni korak: uporabnik najprej izbere barvno paleto drevesa in ozadje, aplikacija ta del genoma zamrzne, evolucija pa nato deluje na obliki drevesa. Alternativa brez dodatnega koraka je prva generacija z namenoma čim bolj razpršenimi paletami, tako da uporabnik že v prvi generaciji vidi celoten razpon možnih barvnih shem.
== Zgodovina == Zgodovina
Uporabniki so želeli zgodovino. To bi bilo dokaj enostavno dodati, saj hranimo celotno stanje aplikacije v sistemu, da lahko stanje preživi izklop aplikacije ampak se med izdelavo nismo spomnili, da bi dodali zgodovino. Uporabniki so želeli zgodovino. To bi bilo dokaj enostavno dodati, saj hranimo celotno stanje aplikacije v sistemu, da lahko stanje preživi izklop aplikacije ampak se med izdelavo nismo spomnili, da bi dodali zgodovino.
@@ -1427,7 +1417,7 @@ Med izdelavo aplikacije smo izdelali _sistem strežnik in odjemalec_, da smo lah
Naš sistem je bil tako narejen, da je imel vsak odjemalec skrivno številko, ki jo je uporabil pri komunikaciji, ampak smo še vedno naleteli na to, da se je lahko nekdo naključno povezal na strežnik in je zadel število, in ker nismo imeli dobre filtracije podatkov, se je vse, kar nam je poslal, direktno shranjevalo v bazo. Bilo bi dobro, da imamo paritetne kode na koncu sporočila, da vemo, ali je sporočilo validno. Ko nam je zlonamerni odjemalec napolnil bazo, smo se odločili, da bomo odstranili vse podatke, ki ne sledijo pravilni evoluciji, in ker smo imeli napako v kodi, smo zbrisali celotno bazo podatkov. Med testiranjem smo našli tudi par primerov, kjer se sporočila niso prenesla v celoti. Naš sistem je bil tako narejen, da je imel vsak odjemalec skrivno številko, ki jo je uporabil pri komunikaciji, ampak smo še vedno naleteli na to, da se je lahko nekdo naključno povezal na strežnik in je zadel število, in ker nismo imeli dobre filtracije podatkov, se je vse, kar nam je poslal, direktno shranjevalo v bazo. Bilo bi dobro, da imamo paritetne kode na koncu sporočila, da vemo, ali je sporočilo validno. Ko nam je zlonamerni odjemalec napolnil bazo, smo se odločili, da bomo odstranili vse podatke, ki ne sledijo pravilni evoluciji, in ker smo imeli napako v kodi, smo zbrisali celotno bazo podatkov. Med testiranjem smo našli tudi par primerov, kjer se sporočila niso prenesla v celoti.
#pagebreak() #pagebreak()
== Gradilni sistem == Gradilni sistem <gradilni_sistem>
Začeli smo z enostavno Makefile datoteko, kjer smo vse enostavno kompilirali skupaj. Začeli smo z enostavno Makefile datoteko, kjer smo vse enostavno kompilirali skupaj.
```Makefile ```Makefile
@@ -1451,7 +1441,7 @@ Nato smo morali dodati še gradnjo enega programa, kar je bilo mogoče z našo s
#pagebreak() #pagebreak()
= ZAKLJUČEK = ZAKLJUČEK <zakljucek>
V tej diplomski nalogi smo razvili aplikacijo Treender, ki uporabnikom omogoča interaktivno vodenje evolucije fraktalnih dreves z uporabo genetskih algoritmov in preprostih uporabniških gest. Cilj projekta je bil raziskati, kako lahko kombinacija algoritmov in uporabniških ocen ustvarja osebne in dinamične izkušnje. V tej diplomski nalogi smo razvili aplikacijo Treender, ki uporabnikom omogoča interaktivno vodenje evolucije fraktalnih dreves z uporabo genetskih algoritmov in preprostih uporabniških gest. Cilj projekta je bil raziskati, kako lahko kombinacija algoritmov in uporabniških ocen ustvarja osebne in dinamične izkušnje.
@@ -1461,6 +1451,10 @@ Za merjenje podobnosti med drevesi smo izbrali Hammingovo razdaljo, ker se raču
Projekt je dosegel zastavljene cilje: uporabnikom omogoča, da s 16 potezami na generacijo usmerjajo videz fraktalnih dreves, ne da bi morali svoj okus opisati z besedami ali nastavljati parametre. Hkrati je pokazal tudi omejitve izbrane zasnove. Za nadaljnje delo se kot najbolj obetavni kažejo štirje koraki: Projekt je dosegel zastavljene cilje: uporabnikom omogoča, da s 16 potezami na generacijo usmerjajo videz fraktalnih dreves, ne da bi morali svoj okus opisati z besedami ali nastavljati parametre. Hkrati je pokazal tudi omejitve izbrane zasnove. Za nadaljnje delo se kot najbolj obetavni kažejo štirje koraki:
- uvodna izbira barvne palete in ozadja, ki odpravi težavo hladnega zagona (@vsiljena_paleta_ref);
- prilagodljiva stopnja naključnosti, ki je v prvih generacijah večja, da uporabnik hitro vidi celoten razpon možnosti, nato pa se manjša za natančnejše uravnavanje podrobnosti;
- eksperimentalno določanje velikosti generacije in stopnje mutacije z merjenjem hitrosti konvergence, namesto sedanjih vrednosti, izbranih med razvojem (@izbira_parametrov);
- zgodovina prikazanih dreves in preimenovanje prikazane podobnosti v skladu s tem, kar dejansko meri.
#pagebreak() #pagebreak()
#bibliography("citations.bib", title: [VIRI IN LITERATURA], style: "ieee.csl") #bibliography("citations.bib", title: [VIRI IN LITERATURA], style: "ieee.csl")