Compare commits

6 Commits
Author SHA1 Message Date
nikola cc4c35fd40 WIP 2026-09-04 15:42:18 +02:00
nikola 8748910e7d Dodal oznake na grafih 2026-07-15 17:37:52 +02:00
nikola 16eb2ab6ef resize and convert to webp 2026-07-15 16:50:09 +02:00
nikola 728342951a Done lektoriranje 2026-05-31 22:36:09 +02:00
nikola b922122a1f tmp 2026-05-27 22:15:11 +02:00
nikola 454480888f DNA 2026-05-26 20:32:23 +02:00
15 changed files with 261 additions and 272 deletions
+26 -26
View File
@@ -1,106 +1,106 @@
#show figure.where(kind: image): set figure(supplement: [Slika]) #show figure.where(kind: image): set figure(supplement: [Slika])
#figure( #figure(
image("slike/full_generation/1761153347_0.png"), image("assets/full_generation/1761153347_0.webp"),
caption: [ caption: [
Generacija 0 Generacija 0
] ],
) )
#pagebreak() #pagebreak()
#figure( #figure(
image("slike/full_generation/1761153347_1.png"), image("assets/full_generation/1761153347_1.webp"),
caption: [ caption: [
Generacija 1 Generacija 1
] ],
) )
#pagebreak() #pagebreak()
#figure( #figure(
image("slike/full_generation/1761153347_2.png"), image("assets/full_generation/1761153347_2.webp"),
caption: [ caption: [
Generacija 2 Generacija 2
] ],
) )
#pagebreak() #pagebreak()
#figure( #figure(
image("slike/full_generation/1761153347_3.png"), image("assets/full_generation/1761153347_3.webp"),
caption: [ caption: [
Generacija 3 Generacija 3
] ],
) )
#pagebreak() #pagebreak()
#figure( #figure(
image("slike/full_generation/1761153347_4.png"), image("assets/full_generation/1761153347_4.webp"),
caption: [ caption: [
Generacija 4 Generacija 4
] ],
) )
#pagebreak() #pagebreak()
#figure( #figure(
image("slike/full_generation/1761153347_5.png"), image("assets/full_generation/1761153347_5.webp"),
caption: [ caption: [
Generacija 5 Generacija 5
] ],
) )
#pagebreak() #pagebreak()
#figure( #figure(
image("slike/full_generation/1761153347_6.png"), image("assets/full_generation/1761153347_6.webp"),
caption: [ caption: [
Generacija 6 Generacija 6
] ],
) )
#pagebreak() #pagebreak()
#figure( #figure(
image("slike/full_generation/1761153347_7.png"), image("assets/full_generation/1761153347_7.webp"),
caption: [ caption: [
Generacija 7 Generacija 7
] ],
) )
#pagebreak() #pagebreak()
#figure( #figure(
image("slike/full_generation/1761153347_8.png"), image("assets/full_generation/1761153347_8.webp"),
caption: [ caption: [
Generacija 8 Generacija 8
] ],
) )
#pagebreak() #pagebreak()
#figure( #figure(
image("slike/full_generation/1761153347_9.png"), image("assets/full_generation/1761153347_9.webp"),
caption: [ caption: [
Generacija 9 Generacija 9
] ],
) )
#pagebreak() #pagebreak()
#figure( #figure(
image("slike/full_generation/1761153347_10.png"), image("assets/full_generation/1761153347_10.webp"),
caption: [ caption: [
Generacija 10 Generacija 10
] ],
) )
#pagebreak() #pagebreak()
#figure( #figure(
image("slike/full_generation/1761153347_11.png"), image("assets/full_generation/1761153347_11.webp"),
caption: [ caption: [
Generacija 11 Generacija 11
] ],
) )
#pagebreak() #pagebreak()
#figure( #figure(
image("slike/full_generation/1761153347_12.png"), image("assets/full_generation/1761153347_12.webp"),
caption: [ caption: [
Generacija 12 Generacija 12
] ],
) )
Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

+246 -257
View File
@@ -236,6 +236,7 @@ Ko vzorec ocenimo, si aplikacija zapomni, in ko ocenimo vse vzorce, se nam prika
#pagebreak() #pagebreak()
= UPORABLJENE TEHNOLOGIJE = UPORABLJENE TEHNOLOGIJE
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 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.
@@ -357,47 +358,42 @@ CMake je močno orodje, ki olajša in avtomatizira proces gradnje programske opr
== SQLITE3 == 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. *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: === 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. 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. Datoteka je preprosto kopirljiva med različnimi sistemi, ne da bi potrebovali dodatne konfiguracije. 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. 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 uporabljate v komercialnih in nekomercialnih projektih.
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: === 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. - *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 pametni ure, senzorji in druga naprava z omejenimi viri. - *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. - *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. - *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. - *Aplikacije za namizje:* Številne namizne aplikacije uporabljajo SQLite za lokalno shranjevanje podatkov, saj ne zahteva dodatne infrastrukture.
SQLite je torej idealna rešitev, če potrebujete preprosto, hitro in zanesljivo podatkovno bazo, ki deluje neposredno v vaši aplikaciji. Je ena najpogosteje uporabljanih podatkovnih baz na svetu, predvsem zaradi svoje preprostosti in učinkovitosti. 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() #pagebreak()
= STRUKTURA APLIKACIJE = STRUKTURA APLIKACIJE
Aplikacija ima tri glavne enote DNA menedžer, DNA vizualizator in Jedro. DNA manedžer skrbi za shranjevanje trenutnega dna in mutacijo, DNA vizualizator pa skrbi za izris dna, njegovo delo je, da dobi dna sekvenco in jo pretvori v sliko. V jedru se pa nahajajo ostale komponente, ki so pomembne za delovanje aplikacije, 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, skrbi za premik in rotacijo slike ko s prstom povlečemo po zaslonu, skrbi da, ko pridemo s prstom do roba in hočemo označiti sliko da se izbira pošlje v dna manager ki nam nato vrne nasledni dna za prikaz jedro tukaj poskrbi da se prenese v dna vizualizator in ko dobi sliko, da jo prikaže uporabniku, po vsaki izbiri se izbira tudi shrani na disk, ko pridemo pa do konca generacije se še cela generacija shrani na disk in nato se tudi 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.
#figure( #figure(
image("assets/potek_diagram/diagram.svg", height: 400pt), image("assets/potek_diagram/diagram.svg", height: 400pt),
@@ -406,23 +402,23 @@ 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
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 uporabo trigometrije in uporabe trigometrije.
=== Prvi korak: Ob pritisku miške === Prvi korak: Ob pritisku miške
V prvem koraku moramo izračunati velikost(magnituda) in kot vektorja med miško in levim spodnim kotom slike (Cyan vektor @Prikaz_vektorjev) in shraniti si moramo trenutno pozicijo miške (Oranžni vektor/Zelena pika @Prikaz_vektorjev) V prvem koraku moramo izračunati velikost (magnitudo) in kot vektorja med miško in levim spodnjim kotom slike (Cyan vektor @Prikaz_vektorjev) in shraniti si moramo trenutno pozicijo miške (Oranžni vektor/Zelena pika @Prikaz_vektorjev).
$ $
"mouseStartV" = "mouseV"\ "mouseStartV" = "mouseV"\
"sizeOfVectorMI" = sqrt(("mouseV"_x - "imageV"_x)^2 + ("mouseV"_y - "imageV"_y)^2)\ "sizeOfVectorMI" = sqrt(("mouseV"_x - "imageV"_x)^2 + ("mouseV"_y - "imageV"_y)^2)\
"oldAngleOfVectorMI" = "atan2"("imageV"_x - "mouseV"_x, "imageV"_y - "mouseV"_y) "oldAngleOfVectorMI" = "atan2"("imageV"_x - "mouseV"_x, "imageV"_y - "mouseV"_y)
$ $
- $"mouseV"$ - trenutni vektor miške - $"mouseV"$ trenutni vektor miške
- $"imageV"$ - vektor pozicije slike (Modri vektor @Prikaz_vektorjev) - $"imageV"$ vektor pozicije slike (Modri vektor @Prikaz_vektorjev)
- $"vectorMI"$ - vectorMouseImage - vector med miško in sliko - $"vectorMI"$ vectorMouseImage vector med miško in sliko
- $"sizeOfVectorMI"$ - velikost vektorjaMI - $"sizeOfVectorMI"$ velikost vektorjaMI
- $"oldAngleOfVectorMI"$ - kot vektorjaMI v radianih - $"oldAngleOfVectorMI"$ kot vektorjaMI v radianih
#grid( #grid(
columns: 2, columns: 2,
@@ -443,7 +439,7 @@ $
#pagebreak() #pagebreak()
=== Drugi korak: Medtem ko miško držimo stisnjeno === Drugi korak: Medtem ko miško držimo stisnjeno
Sedaj lahko izračunamo rotacijo slike glede na horizontalni premik miške,in posledično novo pozicijo slike. Sedaj lahko izračunamo rotacijo slike glede na horizontalni premik miške in posledično novo pozicijo slike.
$ $
Delta"mouseV"_x & = "mouseV"_x - "mouseStart"_x \ Delta"mouseV"_x & = "mouseV"_x - "mouseStart"_x \
@@ -453,65 +449,46 @@ $
"rotationRad" & = (("rotation"* pi) / 180.0) "rotationRad" & = (("rotation"* pi) / 180.0)
$<mat_rotacija> $<mat_rotacija>
- $"screenWidth"$ - širina zaslona - $"screenWidth"$ širina zaslona
- $Delta"mouseV"_x$ - sprememba pozicije $[-"screenWidth", "screenWidth"]$ - $Delta"mouseV"_x$ sprememba pozicije $[-"screenWidth", "screenWidth"]$
- $a$ - normalizirano na razpon $[-1, 1]$ - $a$ normalizirano na razpon $[-1, 1]$
- $b$ - normalizirano na razpon $[0, 1]$ - $b$ normalizirano na razpon $[0, 1]$
- $"rotation"$ - kot za koliko se more slika obrniti v stopinjah - $"rotation"$ kot za koliko se more slika obrniti v stopinjah
- $"rotationRad"$ - v radianih - $"rotationRad"$ v radianih
#v(10pt) #v(10pt)
V @mat_rotacija Za $Delta"mouseV"$ lahko vidimo v (@Prikaz_razdalje) da, ko je začetek v zeleni piki in se premikamo proti modri je delta pozitivna in ko se premikamo proti rdeči je negativna zato potrebujemo normalizacijo $a$ in $b$ ker lerp sprejme vrednosti med $[0, 1]$ V @mat_rotacija za $Delta"mouseV"$ lahko vidimo na (@Prikaz_razdalje), da ko je začetek v zeleni piki in se premikamo proti modri, je delta pozitivna in ko se premikamo proti rdeči, je negativna, zato potrebujemo normalizacijo $a$ in $b$ ker LERP sprejme vrednosti med $[0, 1]$.
#v(10pt) #v(10pt)
$ $
"newAngleOfVectorMI" & = "rotationRad" + "oldAngleOfVectorM" \ "newAngleOfVectorMI" & = "rotationRad" + "oldAngleOfVectorM" \
"vectorMI"_x & = "sizeOfVecorMI" * cos("newAngleOfVectorMI") \ "vectorMI"_x & = "sizeOfVectorMI" * cos("newAngleOfVectorMI") \
"vectorMI"_y & = "sizeOfVecorMI" * sin("newAngleOfVectorMI") \ "vectorMI"_y & = "sizeOfVectorMI" * sin("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"
$ $
- $"imageRotation"$ - novi kot slike v stopinjah - $"imageRotation"$ novi kot slike v stopinjah
- $"imageV"$ - pozicija slike - $"imageV"$ pozicija slike
#v(10pt) #v(10pt)
Sedaj lahko končno narišemo sliko na poziciji $"imageV"$ in pot kotom $"imageRotation"$ Sedaj lahko končno narišemo sliko na poziciji $"imageV"$ in pot kotom $"imageRotation"$.
#pagebreak() #pagebreak()
=== Tretji korak: Ob spustitvi miške === Tretji korak: Ob spustitvi miške
Ko miško spustimo moramo samo ponastaviti vrednosti slike da je spet pokončno in jo postaviti nazaj na originalno pozicijo. Ko miško spustimo, moramo samo ponastaviti vrednosti slike, da je spet pokončno, in jo postaviti nazaj na originalno pozicijo.
$ $
"imageRotation" & = 90 \ "imageRotation" & = 90 \
"imageV" & = "orgImageV" "imageV" & = "orgImageV"
$ $
- $"orgImageV"$ - originalna pozicija slike - $"orgImageV"$ originalna pozicija slike
=== Uporabljene funkcije === Uporabljene funkcije
==== atan2 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.
Funkcija atan2 je matematična funkcija, ki se uporablja za izračun tangente loka količnika, vendar namesto enega samega razmerja ($y/x$) vzame dva ločena argumenta ($y$ in $x$). Zaradi tega je še posebej uporaben za določanje kota v pravilnem kvadrantu, ki temelji na znakih obeh koordinat. @wiki_atan2:
$
"atan2"(x, y) := cases(
arctan(frac(y, x)) & "if" x > 0,
arctan(frac(y, x))+pi & "if" x<0 "and" y >= 0,
arctan(frac(y, x))-pi & "if" x<0 "and" y < 0,
+ frac(pi, 2) & "if" x = 0 "and" y > 0,
- frac(pi, 2) & "if" x = 0 "and" y < 0,
"undefined" & "if" x = 0 "and" y = 0
)
$
==== LERP
Lerp (okrajšava za linearno interpolacijo) je matematična tehnika, ki se uporablja za iskanje vrednosti med dvema danima vrednostma na podlagi oločenega interpolacijskega parametra, ki je pogosto označen kot $t$. @wiki_lerp
$
"lerp"(v 0, v 1, t) = v 0 + t * (v 1 - v 0)
$
#pagebreak() #pagebreak()
@@ -520,16 +497,12 @@ $
#let like = `like` #let like = `like`
#let newGen = `newGen` #let newGen = `newGen`
Namenjena uporaba je, najprej pokličemo #next, ki nam vrne objekt z DNA podatki in indeks DNA-a, ko se uporabnik odloči ali mu je všec, to sporočimo managerju preko funkcije #like, ko nam #like vrne `false` in #next vrne `nullptr` vemo, da smo prikazali in označili vse DNA-je v generaciji in moramo generirati novo generacijo s klicom #newGen. (@dna_manager) DNA menedžer hrani celotno populacijo tekoče generacije, skrbi za vrstni red prikaza osebkov in za prehod v naslednjo generacijo. Vmesnik sestavljajo tri funkcije (@dna_manager): #next vrne naslednji osebek za prikaz, #like sporoči uporabnikovo oceno, #newGen pa iz zbranih ocen zgradi naslednjo generacijo. Ko #next vrne `nullptr` in #like vrne `false`, so vsi osebki generacije prikazani in ocenjeni, zato jedro pokliče #newGen.
Dna manager nam dovoli, da večkrat pridobimo naslednji DNA brez, da takoj označimo ali nam je všeč. V aplikaciji je to uporabljeno tako da pridobimo dva DNA za prikaz. Prvi je na vrhu in ga lahko premikamo in označimo, drugi je pa v ozadju. Po označitvi se zgornji in spodnji zamenjata in spodnji se zamenja z naslednjim. 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.
Funkcija #next vrne DNA iz `voctor[queued]` in ga nato inkrementira `queued` če je `queued` večji od `vector` vrne `nullptr`.
Funkcija #like sprejme indeks in liked in nato shrani index v vector liked ali disliked, nato inkrementira `seen` in če je večji od `vector` vrne `false`.
Medtem funkcija #newGen vsebuje glavno logiko. Deluje na principu naravne evolucije naključno izbere dva DNA in ustvari otroka tako da naključno kopira DNA od obeh staršev, da ima v povprečju 50% DNA od enega starša in 50% od drugega starša. Ko ustvari novo generacijo celotno generacijo mutira tako da naključno spremeni specificirano število genov(byte). Tukaj lahko upazimo robni primer kaj če imamo samo en ali nič DNA-jev ki so uporabniku všeč, v primeru da kloniramo dokler ne ustvarimo novo generacijo in v primeru ko uporabniku ni noben všeč generiramo novo naključno generacijo.
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.
#figure(caption: [DnaManager])[ #figure(caption: [DnaManager])[
#grid( #grid(
columns: 2, columns: 2,
@@ -541,7 +514,7 @@ Medtem funkcija #newGen vsebuje glavno logiko. Deluje na principu naravne evoluc
uint128 randSeed; uint128 randSeed;
int64_t id; int64_t id;
int queued; int queued;
int seen; int showed;
std::vector<Dna> vector; std::vector<Dna> vector;
std::vector<int> liked; std::vector<int> liked;
std::vector<int> disliked; std::vector<int> disliked;
@@ -578,8 +551,8 @@ Medtem funkcija #newGen vsebuje glavno logiko. Deluje na principu naravne evoluc
#pagebreak() #pagebreak()
== DNA == ZAPIS DNA
Dna je sestavljen iz dveh struktur, (@dna_objekt). Zanimljiv 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])[
@@ -633,7 +606,7 @@ Dna je sestavljen iz dveh struktur, (@dna_objekt). Zanimljiv del ki ga opazimo j
#pagebreak() #pagebreak()
Če pogledamo definicijo od `newDna` (@dna_funkcije) vidimo pretvorbo(cast) iz `Dna` v `uint8_t*` in sedaj se lahko enostavno sprehodimo čez objekt in spremenimo vrednosti. Če pogledamo definicijo od `newDna` (@dna_funkcije), vidimo pretvorbo (cast) iz `Dna` v `uint8_t*` in sedaj se lahko enostavno sprehodimo čez objekt in spremenimo vrednosti.
#figure(caption: [Primer DNA funkcije])[ #figure(caption: [Primer DNA funkcije])[
```cpp ```cpp
@@ -656,10 +629,10 @@ Dna je sestavljen iz dveh struktur, (@dna_objekt). Zanimljiv del ki ga opazimo j
``` ```
]<dna_funkcije> ]<dna_funkcije>
Ostale funkcije so podobne najprej pretvorimo v `uint_8*` in nato: Ostale funkcije so podobne; najprej pretvorimo v `uint_8*` in nato:
- `makeChild` naključno kopiramo iz staršev - `makeChild` naključno kopiramo iz staršev,
- `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.
@@ -673,7 +646,7 @@ Ostale funkcije so podobne najprej pretvorimo v `uint_8*` in nato:
caption: [Končna slika po vizualizaciji], caption: [Končna slika po vizualizaciji],
) )
Je sestavljena iz treh razredov `Canvas`, `BackGround`, `Tree`(@razredi_vizualizacije). Tukaj Canvas je samo umesni razred ki postavi risanje na teksturo in nato pokliče BackGround in Tree Je sestavljena iz treh razredov: `Canvas`, `BackGround`, `Tree` (@razredi_vizualizacije). Tukaj je Canvas samo vmesni razred, ki postavi risanje na teksturo in nato pokliče BackGround in Tree.
#figure(caption: [Razredi za vizualizacijo])[ #figure(caption: [Razredi za vizualizacijo])[
#grid( #grid(
@@ -731,22 +704,11 @@ Je sestavljena iz treh razredov `Canvas`, `BackGround`, `Tree`(@razredi_vizualiz
#pagebreak() #pagebreak()
=== Ozadje === Ozadje
Postopek izrisa ozadja je sestavljen iz 5 korakov Ozadje izrišemo v štirih korakih. Najprej narišemo nebo, in sicer podnevi kot navpičen barvni prehod s funkcijo `DrawRectangleGradientV` (@steps_nebo), ponoči pa kot enobarvno površino z zvezdami, katerih položaje določa naključno seme iz genoma (@steps_zvezde). Sledita sonce oziroma luna, sestavljena iz več koncentričnih krogov z nizko prosojnostjo, da dobimo mehak rob (@steps_sonce). Nazadnje izrišemo tri plasti gorovja, vsako s svojo barvo in višinskim razponom (@steps_gora_1 do @steps_gora_5).
+ Nebo je narisano z pomočjo raylib funkcije `DrawRectangleGradientV`(@steps_nebo) Gore so bile med vsemi elementi najzahtevnejše. Rešili smo jih s trakom trikotnikov (triangle strip) @wiki_triangle_strip, pri katerem se točke izmenjujejo med tlemi in naključno izbrano višino, tako da vsaka nova točka s prejšnjima dvema tvori trikotnik (@Triangle_Strip). Plast izrišemo iz 150 takih segmentov od levega do desnega roba slike.
+ Če je noč, zvezde narišemo z naključnim semenom iz DNA ki se uporabi za pozicijo, in funkcijo `DrawTriangle` (@steps_zvezde) 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.
+ Sonce in Luna so narisane z raylib funkcijo `DrawCircle` (@steps_sonce)
+ Gore so bile težje za narisat odločil sem se da bom uporabil triangle strip, (@Triangle_Strip) @wiki_triangle_strip najprej se odlocimo iz koliko segmentov jo bomo narisali v aplikaciji uporabljamo 150 segmentov s tem imamo širino segmenta. Sedaj sledimo tem korakom:
+ Prvo točko postavimo v spodnjem levem kotu (0,0)
+ Za drugo točko si izberemo naključno številko za višino gore (0,80)
+ Naslednja točka je spet na telh samo zamaknjena za širino segmenta na desno(20,0)
+ Za naslednjo točko si zberemo naključno število za višino gore in zamaknjeno na desno od prejšne višine (20, 90)
+ Sedaj ponavljamo točko 3 in 4 dokler ne pridemo do desnega roba slike
+ Ponovimo korak 4 še dvakrat da dobimo gorovje (@steps_gora_5)
#block(breakable: false)[ #block(breakable: false)[
#grid( #grid(
@@ -755,19 +717,19 @@ Postopek izrisa ozadja je sestavljen iz 5 korakov
[ [
#figure( #figure(
image("assets/draw_steps/steps_1_1.png", height: 100pt), image("assets/draw_steps/steps_1_1.png", height: 100pt),
caption: [Korak nebo], caption: [Korak dnevno nebo],
)<steps_nebo> )<steps_nebo>
], ],
[ [
#figure( #figure(
image("assets/draw_steps/steps_2_2.png", height: 100pt), image("assets/draw_steps/steps_2_2.png", height: 100pt),
caption: [Korak nebo], caption: [Korak nočno nebo z zvezdami],
)<steps_zvezde> )<steps_zvezde>
], ],
[ [
#figure( #figure(
image("assets/draw_steps/steps_1_2.png", height: 100pt), image("assets/draw_steps/steps_1_2.png", height: 100pt),
caption: [Korak sonce], caption: [Korak sonce oziroma luna],
)<steps_sonce> )<steps_sonce>
], ],
@@ -816,23 +778,23 @@ Postopek izrisa ozadja je sestavljen iz 5 korakov
#let da = `DrawArgs` #let da = `DrawArgs`
#let dn = `Branch` #let dn = `Branch`
Algoritem za izris drevesa je iterativni proceduralni generator. Za izdris uporabljamo strukturo #dn (@dna_objekt) in `std::list` (FIFO) #da (@razredi_vizualizacije) Algoritem za izris drevesa je iterativni proceduralni generator. Za izris uporabljamo strukturo #dn (@dna_objekt) in `std::list` (FIFO) #da (@razredi_vizualizacije).
==== DNA Branch ==== DNA Branch
Veja je struktura ki vsebuje podatke o: Veja je struktura, ki vsebuje podatke o:
- Začetni barvi (`colorX`) [0, 255] - začetni barvi (`colorX`) [0, 255],
- Spremembi barve (`colorX_change`) za koliko se barva spremeni čez dolžino veje [0, 255] - spremembi barve (`colorX_change`), za koliko se barva spremeni čez dolžino veje [0, 255],
- Variaciji zečetne barve (`colorVar`) za koliko se lahko naključno spremeni začetna barva [-15, -15] - variaciji začetne barve (`colorVar`), za koliko se lahko naključno spremeni začetna barva [-15, -15],
- Začetna debelina (`size`) [2, 20] - začetni debelini (`size`) [2, 20],
- Debelina glede na starša (`sizeParent`) [0.0, 1.0] - debelini glede na starša (`sizeParent`) [0.0, 1.0],
- Debelina glede na globino (`sizeLevel`) iz @table_glob_data procent koliko je podoben predefinirani velikosti [0.0, 1.0] - debelini glede na globino (`sizeLevel`); iz @table_glob_data procent, koliko je podoben predefinirani velikosti [0.0, 1.0],
- Sprememba debeline (`sizeChange`) za končna debelina veje [-5, 5] - spremembi debeline (`sizeChange`) za končno debelino veje [-5, 5],
- Variacija debeline (`sizeVar`) za koliko se lahko naključno spremeni začetna debelina [-5, 5] - variaciji debeline (`sizeVar`), za koliko se lahko naključno spremeni začetna debelina [-5, 5],
- Dolžina (`length`) veje [0.5, 1.3] - dolžini (`length`) veje [0.5, 1.3],
- Variacija dolžine (`lengthVar`) za koliko se lahko nakključno spremeni dolžina veje [-0.15, 0.15] - variaciji dolžine (`lengthVar`), za koliko se lahko naključno spremeni dolžina veje [-0.15, 0.15],
- Stevilo otrokov (`branchCount`) koliko vej bo nadaljevalo [2, 3] - številu otroki (`branchCount`), koliko vej bo nadaljevalo [2, 3],
- Variacija kota med vejami (`branchAngleVar`) naključna variacija kota med vejami za to da ni enakomerno razporejeno [0, 20] - variaciji kota med vejami (`branchAngleVar`) naključna variacija kota med vejami zato, da ni enakomerno razporejeno [0, 20].
#figure(caption: [Globalni podatki])[ #figure(caption: [Globalni podatki])[
#table( #table(
@@ -847,9 +809,10 @@ Veja je struktura ki vsebuje podatke o:
#pagebreak() #pagebreak()
// TODO: Dodaj slike za lazjo predstavo
==== Algoritem ==== Algoritem
Začnemo tako, da damo v list @prvi_drawArgs in ponavljamo naslednje postopke dokler imamo nekaj v listi 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.
#figure(caption: [Prvi DrawArgs])[ #figure(caption: [Prvi DrawArgs])[
```cpp ```cpp
struct DrawArgs struct DrawArgs
@@ -861,11 +824,10 @@ Začnemo tako, da damo v list @prvi_drawArgs in ponavljamo naslednje postopke do
``` ```
]<prvi_drawArgs> ]<prvi_drawArgs>
+ Korak: Predpostavek + Korak: Predpostavek, da lahko narišemo drevo, predvidevamo, da imamo strukturo `DNA`
Da lahko narišemo drevo predvidevamo da imamo strukturo `Dna`
+ Korak: Priprava + Korak: Priprava
Pridobimo `DrawArgs arg` in `Branch b` Pridobimo `DrawArgs arg` in `Branch b`
```cpp ```cpp
@@ -1024,7 +986,7 @@ Začnemo tako, da damo v list @prvi_drawArgs in ponavljamo naslednje postopke do
=== Optimizacija izrisa === Optimizacija izrisa
Sprava se je drevo izrisalo s pomočjo raylib funkcije `DrawLineEx`, drevo se je izrisevalo vsak okvir(frame). Kasneje smo hoteli dodali veje z spremenljivo debelino, najlažja rešitev ki smo se jo spomnili je bila da narišemo krogce z spremenljivim polmerom. To nas je prisililo da moramo narisati čez 55,000 krogov. Z raylib-ovo 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 ševedno pojavil zaostanek(lag spoke), ki smo ga rešili tako da smo omejili število risalnih klicov na okvir in drevo narisali v parih okvirih. 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.
#align(center)[ #align(center)[
#figure(caption: [Optimizacije izrisa])[ #figure(caption: [Optimizacije izrisa])[
@@ -1035,19 +997,20 @@ Sprava se je drevo izrisalo s pomočjo raylib funkcije `DrawLineEx`, drevo se je
[Optimizacija], [Povprečen ms/okvir], [Skupni čas izrisa slike], [Maks ms/okvir po zamenjavi slike], [FPS] [Optimizacija], [Povprečen ms/okvir], [Skupni čas izrisa slike], [Maks ms/okvir po zamenjavi slike], [FPS]
), ),
table.hline(), table.hline(),
[Izris z črtami], [2], [2], [3], [350], [Izris s črtami], [2], [2], [3], [350],
[Izris z krogi vsak okvir], [100], [100], [100], [10], [Izris s krogi vsak okvir], [100], [100], [100], [10],
[Izris z krogi v teksturo], [0.2], [100], [100], [5000], [Izris s krogi v teksturo], [0.2], [100], [100], [5000],
[Izris z krogi v teksturo čez več okvirov], [0.2], [100], [14], [5000], [Izris s krogi v teksturo čez več okvirjev], [0.2], [100], [14], [5000],
) )
] ]
] ]
#pagebreak() #pagebreak()
=== DNA manager optimizacija === Optimizacija DNA menedžerja
Na zažetku (@dna_manager_v1) je DnaManager samo bil struktura kazalcev na `Dna`, s tem imamo problem da nemoremo shraniti kazalcev v datoteko. Smo spremenili na indekse (@dna_manager_v2), ki se dajo zapisati v datoteko, kasneje smo se spomnili da bi bilo dobro za predpomnilnik da so indeksi v queued sortirani. In med razhroscevanjem smo opazili da queued in showed delujeta kot premikajoči indeksi za queued, pred njim so stvari ki so že prikazane za njim pa so stvari, ki jih še moramo prikazati. Za to smo spremenili queued in showed v en indeks namesto list (@dna_manager_v3). 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.
#align(center)[ #align(center)[
#grid( #grid(
columns: 2, columns: 2,
@@ -1102,111 +1065,103 @@ Na zažetku (@dna_manager_v1) je DnaManager samo bil struktura kazalcev na `Dna`
#pagebreak() #pagebreak()
= PODOBNOST = PODOBNOST
Da smo lahko prikazali koliko so uporabniku slike všeč. Smo se odločili, da bomo izračunali podobnost med slikami, bolj ko so si slike podobne bolj so všeč uporabniku in obratno. Za to smo preučili sedem algoritmov. 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.
== Opis algoritmov 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".
=== Evklidska razdalja
Evklidska razdalja je najkrajša razdalja med dvema točkama v evklidskem prostoru, ki je najpogosteje uporabljen koncept razdalje v matematiki in fiziki. @wiki_euclidean_distance
==== Formula Ker se mera računa po vsaki generaciji in se prikaže uporabniku, mora biti izračunljiva neposredno nad 180-bajtnim genomom, imeti zalogo vrednosti, ki jo je mogoče prikazati kot odstotek, med konvergenco populacije naraščati in se izračunati dovolj hitro, da prehod med generacijama ni opazen. Da bi pokrili različne pristope k merjenju razdalje, smo preučili sedem mer iz treh družin: geometrijske (evklidska razdalja, skalarni produkt, kosinusna podobnost in njena varianta), množične (Jaccardov indeks) ter nizovne oziroma poravnalne (Hammingova razdalja, Levenshteinova razdalja in Needleman-Wunschev algoritem).
Za dve točki p in q v n-dimenzionalnem prostoru se izračuna:
$ d(p, q) = sqrt(sum_(i=1)^n (q_i - p_i)^2) $
==== Normalizacija == Opis mer
Da normaliziramo Evklidsko razdaljo moramo deliti z najdaljšo možno razdaljo v prostoru: Genom obravnavamo kot zaporedje $n = 180$ bajtov, torej kot vektor celih števil z zalogo vrednosti $[0, 255]$. Vse mere so definirane nad tem zapisom in vrnjeno vrednost normaliziramo na odstotke.
$ "maxDistance" = 255 sqrt(n) $
Normalizacija je nato:
$ "normalizedDistance" = frac(d(p, q), "maxDistance") $
=== Skalarni produkt === Geometrijske mere
Skalarni produkt (tudi dot produkt ali notranji produkt) je operacija med dvema vektorjema v vektorskem prostoru, ki kot rezultat vrne skalar (realno število). @wiki_dot_product *Evklidska razdalja* je najkrajša razdalja med dvema točkama v evklidskem prostoru @wiki_euclidean_distance. Normaliziramo jo z največjo možno razdaljo v prostoru, podobnost pa je njen komplement:
==== Formula:
Za vektorja A in B v n-dimenzionalnem prostoru:
$ upright(bold(A)) op(dot) upright(bold(B)) = sum_(i=1)^n A_i B_i $
==== Normalizacija
Da noramliziramo skalarni produkt moramo deliti z največjim produktom v prostoru:
$ "maxDotProduct" = n*255*255 $
Normalizacija je nato:
$ $
"normalizedDotProdut" = frac( d(p, q) & = sqrt(sum_(i=1)^n (q_i - p_i)^2) \
upright(bold(A)) op(dot) upright(bold(B)), s & = 1 - frac(d(p, q), 255 sqrt(n))
"maxDotProduct" $
*Skalarni produkt* dveh vektorjev vrne skalar @wiki_dot_product; normaliziramo ga z največjim možnim produktom $n dot 255^2$:
$
upright(bold(A)) op(dot) upright(bold(B)) & = sum_(i=1)^n A_i B_i \
s & = frac(upright(bold(A)) op(dot) upright(bold(B)), n dot 255^2)
$
*Kosinusna podobnost* meri kosinus kota med vektorjema in je neodvisna od njune velikosti @wiki_cosine_similarity:
$ s = frac(upright(bold(A)) op(dot) upright(bold(B)), ||upright(bold(A))|| ||upright(bold(B))||) $
Ker so vse komponente nenegativne, je kot med vektorjema vedno majhen in mera se že pri naključnih genomih giblje visoko. Zato smo preizkusili tudi *varianto*, pri kateri vrednosti genov najprej preslikamo iz $[0, 255]$ v $[-128, 127]$ in s tem razširimo izhodno območje.
=== Množične mere
*Jaccardov indeks* meri podobnost dveh množic kot razmerje med velikostjo preseka in velikostjo unije @wiki_jaccard_index:
$ J(A, B) = frac(|A #sym.inter B|, |A #sym.union B|) $
=== Nizovne in poravnalne mere
*Hammingova razdalja* meri število mest, na katerih se zaporedji enake dolžine razlikujeta @wiki_hamming_distance. Ker je pri nas gen en bajt, primerjamo bajte in ne posameznih bitov:
$
d(p, q) & = |{i : p_i != q_i}| \
s & = 1 - frac(d(p, q), n)
$
*Levenshteinova razdalja* je najmanjše število vstavitev, izbrisov in zamenjav, potrebnih za pretvorbo enega zaporedja v drugo @wiki_levenshtein_distance. Računa se z dinamičnim programiranjem in ima časovno zahtevnost $O(n^2)$, kar je pri $n = 180$ opazno počasneje od ostalih mer.
*Needleman-Wunschev algoritem* je postopek globalne poravnave zaporedij iz bioinformatike, prav tako zasnovan na dinamičnem programiranju @wiki_needlemanwunsch_algorithm. Vsaki možni poravnavi dodeli oceno glede na izbrane nagrade za ujemanje ter kazni za neujemanje in vrzeli, rezultat pa je poravnava z najvišjo oceno.
#align(center)[
#figure(caption: [Pregled preučenih mer])[
#table(
columns: 4,
align: (left, left, left, left),
table.header([Mera], [Družina], [Časovna zahtevnost], [Testirana]),
table.hline(),
[Evklidska razdalja], [geometrijska], [$O(n)$], [da],
[Skalarni produkt], [geometrijska], [$O(n)$], [da],
[Kosinusna podobnost], [geometrijska], [$O(n)$], [da],
[Kosinusna varianta], [geometrijska], [$O(n)$], [da],
[Jaccardov indeks], [množična], [$O(n)$], [ne],
[Hammingova razdalja], [nizovna], [$O(n)$], [da],
[Levenshteinova razdalja], [poravnalna], [$O(n^2)$], [da],
[Needleman-Wunschev alg.], [poravnalna], [$O(n^2)$], [ne],
) )
$ ]<tabela_mere>
]
=== Kosinusna podobnost Razloga, zakaj Jaccardovega indeksa in Needleman-Wunschevega algoritma nismo testirali, sta navedena v @odlocitev_mera.
Kosinusna podobnost je mera, ki se uporablja za določanje podobnosti dveh vektorjev, ne glede na njuno velikost. Izračuna kosinus kota med dvema vektorjema v n-dimenzionalnem prostoru. @wiki_cosine_similarity
==== Formula:
$
"similarity" = frac(upright(bold(A)) op(dot) upright(bold(B)), ||upright(bold(A))|| ||upright(bold(B))||)
$
==== Variation
Variaciaja, ki jo bomo testirali je da najprej mapiramo vrednosti genov iz (0, 255) v (-127 to 128) in s tem razširimo izhodno območje.
=== Hammingova razdalja
Hammingova razdalja je koncept v matematiki in računalništvu, ki meri razliko med dvema zaporedjema enake dolžine. Najpogosteje se uporablja za nize znakov, binarne zaporedja (npr. bitne nize) ali DNA zaporedja. @wiki_hamming_distance
=== Jaccardov indeks
Jaccardov indeks (ali Jaccardov koeficient podobnosti) je metrika, ki meri podobnost med dvema množicama. Izračuna se kot razmerje med velikostjo presečiška obeh množic in velikostjo njihove unije. @wiki_jaccard_index
==== Formula:
$
J(A, B) = frac(|A #sym.inter B|, |A #sym.union B|)
$
=== Levenshteinova razdalja
Levenshteinova razdalja (ali urejevalna razdalja) je metrika, ki meri razliko med dvema nizoma znakov. Določa najmanjše število urejevalnih operacij (vstavitev, izbris ali zamenjava znakov), potrebnih za pretvorbo enega niza v drugega. @wiki_levenshtein_distance
==== Formula
#let head = "head"
#let tail = "tail"
#let lev = "lev"
#let sif = "if"
$
lev(a, b) := cases(
|a| & sif |b| = 0,
|b| & sif |a| = 0,
lev(tail(a), tail(b)) & sif head(a) = head(b),
1 + min
cases(
lev(tail(a), b),
lev(a, tail(b)),
lev(tail(a), tail(b)),
) & "otherwise"
)
$
Where the
tail(x) is a string of all but the first character of x
head(x) is first character of x
=== Needleman-Wunschev algoritem
Needleman-Wunschov algoritem je algoritem, ki se uporablja v bioinformatiki za poravnavo beljakovinskih ali nukleotidnih zaporedij. Bil je ena prvih aplikacij dinamičnega programiranja za primerjavo bioloških zaporedij. Včasih se imenuje tudi algoritem optimalnega ujemanja in tehnika globalne poravnave. Needleman-Wunschov algoritem se še vedno široko uporablja za optimalno globalno poravnavo, zlasti kadar je kakovost globalne poravnave najpomembnejša. Algoritem dodeli oceno vsakemu možnemu poravnavanju, njegov namen pa je najti vsa možna poravnavanja z najvišjo oceno. @wiki_needlemanwunsch_algorithm
#pagebreak()
== Testiranje algoritmov == Testiranje algoritmov
Testiranje je potekalo na večjih primerih evolucije. Spodaj je prikazani en primer rezultatov podobnosti in časa računanja podobnosti v mikrosecundah. Slike generacij so v prilogi. Mere smo primerjali na sejah evolucije, shranjenih v podatkovni bazi aplikacije. Na voljo smo imeli tri seje; vsaka je bila izpeljana do dvanajste generacije, torej obsega 13 generacij (0--12) in 208 uporabnikovih ocen, skupaj 624 ocen. Vse mere so bile izračunane na istih, že posnetih generacijah, tako da so med seboj neposredno primerljive.
// TODO: dopolniti, kdo je izvedel te tri seje (avtor / testni uporabniki) -- mentorjevo vprašanje "Kdo je testiral?"
V nadaljevanju so prikazani rezultati seje, katere slike vseh generacij so v prilogi. Za to sejo @tabela_vsecki_gen prikazuje tudi, koliko osebkov je uporabnik v posamezni generaciji označil kot všečne, saj je od tega odvisno, katera veja postopka tvorjenja nove generacije se je izvedla (@genetski_algoritem).
#align(center)[
#figure(caption: [Število všečkanih osebkov po generacijah (od 16)])[
#table(
columns: 14,
table.header([Generacija], [0], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [11], [12]),
table.hline(),
[Všečkani], [4], [3], [11], [14], [12], [15], [16], [1], [2], [4], [2], [7], [11],
)
]<tabela_vsecki_gen>
]
=== Testno okolje === Testno okolje
Algoritmi so bili testirani na računalniku z AMD Ryzen 5 5500U procesorjem in 16 GB rama. Vsak algoritem je bil zagnan petkrat in podatki so povprečje rezultatov. Algoritmi so bili testirani na računalniku z AMD Ryzen 5 5500U procesorjem in 16 GB rama. Vsak algoritem je bil zagnan petkrat in podatki so povprečje rezultatov.
// TODO: Dodaj opis testnega okolja
=== Rezultati podobnosti === Rezultati podobnosti
#align(center)[ #align(center)[
#figure(caption: [Rezultati podobnosti])[ #figure(caption: [Rezultati podobnosti])[
#table( #table(
columns: 7, columns: 7,
table.header([generacija], [evklidij], [dot], [cos\_sim], [cos\_sim\_var], [hamming], [levenshtein]), table.header(
[Generacija],
[Evklidska],
[Skalarni produkt],
[Kosinusna],
[Kosinusna variacija],
[Hamming-ova],
[Levenshte-inova],
),
table.hline(), table.hline(),
[0], [58.835], [25.4743], [75.0422], [0.19221], [0.449075], [50.3356], [0], [58.835], [25.4743], [75.0422], [0.19221], [0.449075], [50.3356],
[1], [63.2906], [27.6056], [80.0648], [22.6274], [22.8889], [61.4676], [1], [63.2906], [27.6056], [80.0648], [22.6274], [22.8889], [61.4676],
@@ -1235,25 +1190,38 @@ Algoritmi so bili testirani na računalniku z AMD Ryzen 5 5500U procesorjem in 1
#let sh = (0.449075, 22.8889, 44.9491, 46.75, 46.8519, 48.463, 46.6667, 44.9213, 27.3796, 59.213, 67.3981, 81.8981, 80.7778) #let sh = (0.449075, 22.8889, 44.9491, 46.75, 46.8519, 48.463, 46.6667, 44.9213, 27.3796, 59.213, 67.3981, 81.8981, 80.7778)
#let sl = (50.3356, 61.4676, 72.4768, 73.375, 73.4259, 74.2315, 73.3333, 72.4606, 63.6898, 79.6065, 83.6991, 90.9491, 90.3889) #let sl = (50.3356, 61.4676, 72.4768, 73.375, 73.4259, 74.2315, 73.3333, 72.4606, 63.6898, 79.6065, 83.6991, 90.9491, 90.3889)
#align(center)[
#figure(caption: [Podobnost znotraj generacije po posameznih merah])[
#lq.diagram( #lq.diagram(
legend: (position: (100% + .5em, 0%)), legend: (position: (100% + .5em, 0%)),
height: 200pt, height: 200pt,
width: 300pt, width: 300pt,
lq.plot(xs, se, mark: none, label: [euclidean]), xlabel: "Generacija",
lq.plot(xs, sd, mark: none, label: [dot]), ylabel: "Podobnost v %",
lq.plot(xs, scs, mark: none, label: [cos_sim]), lq.plot(xs, se, mark: none, label: [Evklidska]),
lq.plot(xs, scsv, mark: none, label: [cos_sim_var]), lq.plot(xs, sd, mark: none, label: [Skalarni produkt]),
lq.plot(xs, sh, mark: none, label: [hamming]), lq.plot(xs, scs, mark: none, label: [Kosinusna]),
lq.plot(xs, sl, mark: none, label: [levenshtein]), lq.plot(xs, scsv, mark: none, label: [Kosinusna variacija]),
lq.plot(xs, sh, mark: none, label: [Hammingova]),
lq.plot(xs, sl, mark: none, label: [Levenshteinova]),
) )
]<graf_podobnost>
]
#pagebreak() #pagebreak()
=== Čas računanja podobnosti v us === Čas računanja podobnosti v μs
#align(center)[ #align(center)[
#figure(caption: [Čas računanja podobnosti])[ #figure(caption: [Čas računanja podobnosti])[
#table( #table(
columns: 7, columns: 7,
table.header([generation], [euclidean], [dot], [cos\_sim], [cos\_sim\_var], [hamming], [levenshtein]), table.header(
[Generacija],
[Evklidska],
[Skalarni produkt],
[Kosinusna],
[Kosinusna variacija],
[Hamming-ova],
[Levenshte-inova],
),
table.hline(), table.hline(),
[0], [91], [41], [117], [181], [87], [105799], [0], [91], [41], [117], [181], [87], [105799],
[1], [60], [40], [78], [305], [250], [100331], [1], [60], [40], [78], [305], [250], [100331],
@@ -1280,39 +1248,59 @@ Algoritmi so bili testirani na računalniku z AMD Ryzen 5 5500U procesorjem in 1
#let th = (87, 250, 105, 106, 108, 104, 106, 103, 78, 103, 81, 58, 60) #let th = (87, 250, 105, 106, 108, 104, 106, 103, 78, 103, 81, 58, 60)
#let tl = (105799, 100331, 97438, 97529, 96296, 96456, 96510, 97253, 97409, 99816, 98978, 98289, 99663) #let tl = (105799, 100331, 97438, 97529, 96296, 96456, 96510, 97253, 97409, 99816, 98978, 98289, 99663)
#align(center)[
#figure(caption: [
Čas računanja podobnosti. Levenshteinova razdalja je zaradi merila izpuščena iz prikaza; njen čas se giblje med 96 296 μs in 105 799 μs, torej približno tisočkrat nad ostalimi merami.
])[
#lq.diagram( #lq.diagram(
legend: (position: (100% + .5em, 0%)), legend: (position: (100% + .5em, 0%)),
height: 200pt, height: 200pt,
width: 300pt, width: 300pt,
ylim: (0, 350), ylim: (0, 350),
lq.plot(ts, te, mark: none, label: [euclidean]), xlabel: "Generacija",
lq.plot(ts, td, mark: none, label: [dot]), ylabel: "Čas v μs",
lq.plot(ts, tcs, mark: none, label: [cos_sim]), lq.plot(ts, te, mark: none, label: [Evklidska]),
lq.plot(ts, tcsv, mark: none, label: [cos_sim_var]), lq.plot(ts, td, mark: none, label: [Skalarni produkt]),
lq.plot(ts, th, mark: none, label: [hamming]), lq.plot(ts, tcs, mark: none, label: [Kosinusna]),
lq.plot(ts, tl, mark: none, label: [levenshtein]), lq.plot(ts, tcsv, mark: none, label: [Kosinusna variacija]),
lq.plot(ts, th, mark: none, label: [Hammingova]),
lq.plot(ts, tl, mark: none, label: [Levenshteinova]),
) )
]<graf_cas>
]
#pagebreak() #pagebreak()
== Odločitev == Interpretacija konvergence <interpretacija_konvergence>
Odločili smo se da bomo uporabili Hammingovo razdaljo, ker nam je všeč rezultat podobnosti, ker zače pri 0% nadaljuje proti 100% in kako enostavna je implementacija. Čas računanja podobnosti nismo upoštevali ker so vsi razen Levenshteinova razdalja bili manj kot 0.5 milisekunde in je to za nas dovolj hitro. 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.
Opazili ste da Jaccard indeks in Needleman-Wunsch algoritem manjakta v testiranju in to je zato ker: *Naraščanje v generacijah 2--6.* Uporabnik je v teh generacijah označil med 11 in 16 všečkov, zato je bilo staršev veliko, na novo ustvarjenih osebkov pa malo. Populacija se zato hitro poenoti in Hammingova podobnost naraste s približno 45 % na 48 %, kosinusna pa čez 86 %.
- Jaccardov indeks ni primeren za nas, ker deluje na skupini unikatnih vrednosti kar mi nimamo, mi lahko imamo dve isti vrednosti ki predstavljata dva različni lastnosti slike. Zato tudi če bi dobili 100% ujemanje ne bi pomenilo da se sliki 100% ujemata. *Zastoj med generacijama 6 in 7.* V generaciji 6 je bilo všečkanih vseh 16 osebkov. Ker otroci prepišejo samo mesta nevšečnih osebkov, otrok sploh ni bilo in je na populacijo delovala le mutacija treh bajtov na osebek. Podobnost se med tema dvema generacijama praktično ne spremeni (85,3 % proti 85,1 % pri kosinusni meri).
- Needleman-Wunsch alogitem nam pa ne vrne nevtralne stevilke ki bi jo lahko uporabili za rezultat podobnosti.
*Padec pri generaciji 8.* V generaciji 7 je bil všečen samo en osebek. Postopek je zato uporabil vejo `clone`, ki polovico bajtov otroka nadomesti z naključnimi vrednostmi, in 15 od 16 osebkov naslednje generacije je nastalo na ta način. Podobnost posledično strmo pade, pri Hammingovi meri s 44,9 % na 27,4 %. Padec torej ni napaka meritve, ampak pričakovana posledica robnega primera v postopku.
*Strop pri približno 82 %.* Po generaciji 9 podobnost spet naraste in se pri generacijah 11 in 12 ustali okoli 82 % (Hammingova mera). Popolnega ujemanja postopek ne more doseči, ker se mutacija izvede tudi na všečnih osebkih: vsak osebek v vsaki generaciji dobi tri naključne bajte, zato v populaciji vedno ostane nekaj razlik.
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).
== 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.
Dveh mer nismo testirali:
- *Jaccardov indeks* deluje nad množicami unikatnih vrednosti, genom pa je zaporedje, v katerem ista vrednost na dveh različnih mestih pomeni dve različni lastnosti slike. Popolno ujemanje množic zato ne bi pomenilo, da sta sliki enaki.
- *Needleman-Wunschev algoritem* vrne oceno poravnave, ki je odvisna od izbire kazni in nagrad in nima nevtralne zgornje meje, zato je ni mogoče neposredno prikazati kot odstotek.
#pagebreak() #pagebreak()
= Google Play Store = Google Play Store
Da lahko objavimo aplikacijo na Google Play Store potrebujemo Play Console razvijalski račun @web_google_play_console. Koraki za izdelavo računa so zelo enostavni, vpiši potrebne podatke, sprejmi razvojni sporazum, in plačaj registracijo. Naši problemi so se začeli z vnosom podatkov ni so hoteli sprejeti naše telefonske številke, ker ni so mogli preverit, ko smo pa sledili korakom za odstranjevaje težav, ki so večinoma samo bili počakajte en dan in poskusite ponovno, še vedno ni delovalo. Nato smo kontaktirali njihovo pomoč, ki so nam vrnili podoben odgovor da naj čakamo, po tem smo obupali in samo uporabili drugo telefonsko številko. Da lahko objavimo aplikacijo na Google Play Store, potrebujemo Play Console razvijalski račun @web_google_play_console. Koraki za izdelavo računa so zelo enostavni: vpiši potrebne podatke, sprejmi razvojni sporazum in plačaj registracijo. Naši problemi so se začeli z vnosom podatkov, saj niso hoteli sprejeti naše telefonske številke, ker je niso mogli preveriti, ko pa smo sledili korakom za odstranjevaje težav, ki so bili večinoma samo _počakajte en dan in poskusite ponovno_, še vedno ni delovalo. Nato smo kontaktirali njihovo pomoč, ki so nam vrnili podoben odgovor, da naj čakamo. Po tem smo obupali in uporabili drugo telefonsko številko.
V naslednjih korakih moramo naložiti zapakirano aplikacijo, dodati opis applikacije, slike delovanja in politiko varstva osebnih podatkov (@privacy_policy). Po tem pa moramo spraviti aplikacijo čez notranje testiranje, ki je zelo ne specificirano, generalna ideja naj bi bila da potrebujemo 12 testerjev, ki naj bi uporabljali aplikacijo vsaj 12 dni nikjer pa ni omenjeno koliko časa na dan morajo uporabljati aplikacijo da se šteje. Par tednov smo poskušali z prijatelji ugotoviti kdaj postaneš aktiven samo smo brez uspeha. Nato smo se odločili da bi pogledali po internetu kako ta problem rešujejo ostali. Našli smo dve rešitvi. Prva zastonj rešitev, je nekaj forumov na spletu kjer ljudje objavljajo svoje aplikacije in ideja je da si med samo pomagajo s tem da testirajo drug od drugih aplikacije. Droga plačljiva rešitev za katero smo se mi odločili je da najamemo testerja za pribljižno 20€. Z testerjem smo se zmenili kaj potrebujemo in po izmenjavi potrebnih podatko je po dveh tednih aplikacija testirana in lahko nadaljuje na zadnji pregled pred objavo. V naslednjih korakih moramo naložiti zapakirano aplikacijo, dodati opis applikacije, slike delovanja in politiko varstva osebnih podatkov (@privacy_policy). Po tem moramo spraviti aplikacijo čez notranje testiranje, ki je zelo nespecificirano. Generalna ideja naj bi bila, da potrebujemo 12 testerjev, ki naj bi uporabljali aplikacijo vsaj 12 dni, nikjer pa ni omenjeno, koliko časa na dan morajo uporabljati aplikacijo, da se šteje. Par tednov smo s prijatelji poskušali ugotoviti, kdaj postaneš aktiven, ampak smo bili brez uspeha. Nato smo se odločili, da bi pogledali po internetu, kako ta problem rešujejo ostali. Našli smo dve rešitvi. Prva zastonj rešitev je nekaj forumov na spletu, kjer ljudje objavljajo svoje aplikacije in ideja je, da si med sabo pomagajo s tem, da testirajo aplikacije drug drugega. Druga plačljiva, rešitev za katero smo se odločili, je, da najamemo testerja za pribljižno 20 . S testerjem smo se zmenili, kaj potrebujemo, in po izmenjavi potrebnih podatkv je po dveh tednih aplikacija testirana in lahko nadaljuje na zadnji pregled pred objavo.
Ob zadnjem pregledu je postalo zelo naporno. Opis naše aplikacije je navdihnil opis aplikacije Tinder, saj sta aplikaciji konceptno zelo podobni (@zacetni_opis). Takrat so pri Googlu začeli natančno pregledovati aplikacijo, ker do takrat je bilo vsakič, ko smo poslali aplikacijo v pregled, vse odobreno do naslednjega dne, tokrat pa je trajalo nekaj tednov. Njihov odgovor je bil, da opis naše aplikacije ni primeren in krši pravila, čeprav je bil zelo podoben Tindrovemu. Pri tem smo ugotovili, da se je ekipa, ki je to pregledovala, vedla precej pristransko. V skladu z navodili, ki so nam jih poslali, smo popravili vse kršitve pravil. Tako se je ponovilo še nekajkrat, kar je skupaj trajalo skoraj štiri mesece. Zadnja verzija je (@koncni_opis). Ob zadnjem pregledu je postalo zelo naporno. Opis naše aplikacije je navdihnil opis aplikacije Tinder, saj sta aplikaciji konceptno zelo podobni (@zacetni_opis). Takrat so pri Googlu začeli natančno pregledovati aplikacijo, ker do takrat je bilo vsakič, ko smo poslali aplikacijo v pregled, vse odobreno do naslednjega dne, tokrat pa je trajalo nekaj tednov. Njihov odgovor je bil, da opis naše aplikacije ni primeren in krši pravila, čeprav je bil zelo podoben Tindrovemu. Pri tem smo ugotovili, da se je ekipa, ki je to pregledovala, vedla precej pristransko. V skladu z navodili, ki so nam jih poslali, smo popravili vse kršitve pravil. Tako se je ponovilo še nekajkrat, kar je skupaj trajalo skoraj štiri mesece. Zadnja verzija je @koncni_opis.
Po vsem tem je aplikacija končno objavljena na Play Store @web_google_treender Po vsem tem je aplikacija končno objavljena na Play Store @web_google_treender.
#figure(caption: [Začetni opis])[ #figure(caption: [Začetni opis])[
@@ -1406,19 +1394,19 @@ This Privacy Policy is effective as of February 26, 2025. We reserve the right t
= UPORABNIŠKE IZKUŠNJE = UPORABNIŠKE IZKUŠNJE
Po tem ko smo objavili aplikacijo na Play Store smo prosili prijatelje in sodelavce, naj probajo aplikacijo. Med uporabo so naleteli na nekaj pričakovanih in nepričakovanih problemov. Po tem, ko smo objavili aplikacijo na Play Store, smo prosili prijatelje in sodelavce, naj poskusijo aplikacijo. Med uporabo so naleteli na nekaj pričakovanih in nepričakovanih problemov.
== 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 delsno 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
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 stevilo vzorcev na generacijo (trenutno 16) s tem težko izločimo kaj je uporabniku všeč. Med testiranjem pa nam večje število vzorcev ni bilo všeč ker predolgo traja da pridemo skozi generacijo, in vidimo spremembe. In med izdelavo aplikacije, ker smo vedeli kako deluje algoritem, nismo uporabljali aplikacijo kot uporabnik. Namesto da bi izbirali izgled celotne slike smo izbirali specifične lastnosti na slikah. Nekaterim uporabnikom je tudi ratalo to ugotoviti in so s tem ustvarili precej lepe slike. 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.
== Moteč rezultat podobnosti == Moteč rezultat podobnosti
Nekaj uporabnikov nam je sporočilo da jih je motili da jim ni uspelo doseči 100% podobnost. Da lahko to rešimo bi mogoče lahko uporabili kak drugačen algoritem, ali bi pa lahko preslikali rezultat, da vsaka vrednost nad 85% se prikaže kot 100%. 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 %.
== Zgodovina == Zgodovina
Uporabniki so želeli zgodovino. To bi bilo dokaj enostavno dodati saj hranimo celotno stanje aplikacije v sitemu v primeru da lahko stanje preživi izklop aplikacije. Samo med izdelavo se 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.
#pagebreak() #pagebreak()
@@ -1427,20 +1415,20 @@ Uporabniki so želeli zgodovino. To bi bilo dokaj enostavno dodati saj hranimo c
Izkušnje, ki smo jih pridobili med izdelavo aplikacije. Izkušnje, ki smo jih pridobili med izdelavo aplikacije.
== Strežnik in podatkovna baza == Strežnik in podatkovna baza
Med izdelavo aplikacije smo izdelali strežnik in odjemalec sistem da smo lahko pridobili podatke o uporabnikih in njihovih preferencah. To nas je naučilo nekaj stvari. Med izdelavo aplikacije smo izdelali _sistem strežnik in odjemalec_, da smo lahko pridobili podatke o uporabnikih in njihovih preferencah. To nas je naučilo nekaj stvari.
- Omeji število prejetih podatkov na povezavo, da se nemore nekdo samo povezati in začeti pošiljati naključne podatke. - Omeji število prejetih podatkov na povezavo, da senemore nekdo samo povezati in začeti pošiljati naključnih podatkov.
- Omeji kdo se lahko poveže, z uporabo nekega skrivnostnega ključa ali z registracijo. - Omeji, kdo se lahko poveže z uporabo nekega skrivnostnega ključa ali z registracijo.
- Uporabi prepovedni sistem, če se odjemalec lepo ne obnaša mu prepovej povezavo na strežnik. - Uporabi prepovedni sistem, če se odjemalec ne obnaša lepo mu prepovej povezavo na strežnik.
- Med filtriranjem podatkov v podatkovni bazi ikoli ne briši samo označi, da je izbrisano. - Med filtriranjem podatkov v podatkovni bazi nikoli ne briši samo označi, da je izbrisano.
- Naj odjemalec pošlje vse podatke na enkrat. - Naj odjemalec pošlje vse podatke na enkrat.
- Imej paritetne kode - Imej paritetne kode
Naš sistem je bil tako narejen da je vsak odjemalec imel skrivno stevilko ki jo je uporabil pri komunikaciji, samo smo še vedno naleteli da se je naključno lahko nekdo 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. Bi bilo dobro da imamo paritetne kode na koncu sporočila da vemo ali je sporočilo validno. Na to ko nam je zlonamerni udjemalec napolnil bazo smo se odločilo da bomo odstranili vse podatke ki ne sledijo pravilni evoluciji, in ker smo imeli napako v kodi smo si zbrisali celotno bazo podatkov. Med testiranjem smo tudi našli 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
Začeli smo z enostavno Makefile datoteko kjer smo enostavno samo vse kompilirali skupaj. Začeli smo z enostavno Makefile datoteko, kjer smo vse enostavno kompilirali skupaj.
```Makefile ```Makefile
gcc -c raylib.c -o raylib.o gcc -c raylib.c -o raylib.o
@@ -1448,16 +1436,16 @@ g++ -c main.cpp -o main.o
g++ main.o raylib.o -o main g++ main.o raylib.o -o main
``` ```
Ampak je to čez čas postalo, kar naporno za upravljanje, ko se je število datotek povečevalo in razvijali smo na dveh operacijskih sistemih (Linux in Windows) in po tem smo naredili našo največjo napako. Navdihnil nas je tsoding nob.h @git_nob in odločili smo se da bomo napisali svojo skripto za gradno, ki bo pravilno izbrala pravi prevajalnik g++ za Linux in zig c++ za Windows. To je delovalo prej dobbro, vendar smo sčasoma več časa porabili za skripto kot za naš projekt. To je čez čas postalo kar naporno za upravljanje, ko se je število datotek povečevalo in razvijali smo na dveh operacijskih sistemih (Linux in Windows). Potem smo naredili največjo napako. Navdihnil nas je tsoding nob.h @git_nob in odločili smo se da, bomo napisali svojo skripto za gradnjo, ki bo pravilno izbrala pravi prevajalnik g++ za Linux in zig c++ za Windows. To je delovalo precej dobro, vendar smo sčasoma več časa porabili za skripto kot za naš projekt.
Prišlo je do točke kjer je skripta lahko Prišlo je do točke, kjer:
- Izvajala ukaze - je skripta lahko izvajala ukaze,
- Preverjala če se je skripta spremenila in se je nato sama ponovno zgradila - preverjala, če se je skripta spremenila, in se je nato sama ponovno zgradila,
- Dodali smo inkrementalno gradnjo projekta - smo lahko dodali inkrementalno gradnjo projekta,
- Prenesla in kompilirala raylib - je skripta lahko prenesla in kompilirala raylib,
- Na koncu smo celo dodali da lahko skompilira vse datoteke v direktoriju - smo na koncu celo lahko dodali, da lahko skompilira vse datoteke v direktoriju.
Na to smo morali dodati še gradno enega programa, kar je bilo mogoče z našo skripto, vendar nas je motilo, koliko časa smo porabili za vzdrževanje skripte. Odločili smo se da bomo probali CMake, ki je prvo gradno malo počasnejšo saj smo morali samo prenesti raylib in druge odvisnosti, vendar je bilo vse ostalo popolno in je preprosto delovalo. Nato smo morali dodati še gradnjo enega programa, kar je bilo mogoče z našo skripto, vendar nas je motilo, koliko časa smo porabili za vzdrževanje skripte. Odločili smo se, da bomo poskusili CMake, ki je prvo gradnjo malo počasneje saj smo morali samo prenesti raylib in druge odvisnosti, vendar je bilo vse ostalo popolno in je preprosto delovalo.
@@ -1467,11 +1455,12 @@ Na to smo morali dodati še gradno enega programa, kar je bilo mogoče z našo s
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.
Za razvoj smo uporabljali tehnologije, kot so C++, Raylib in SQLite3, ki so omogočile učinkovito delovanje in vizualizacijo. Ključni izziv je bila optimizacija algoritmov za generiranje in risanje dreves, ki smo ga rešili z risanjem na teksturo in omejevanjem števila risalnih klicev. Ugotovili smo, da je pomembno, da je uporabniški vmesnik intuitiven, saj so uporabniki na začetku težave razumeli osnovne funkcije, kot je ocenjevanje s povlekanjem slike. Za razvoj smo uporabljali tehnologije, kot so C++, Raylib in SQLite3, ki so omogočile učinkovito delovanje in vizualizacijo. Ključni izziv je bila optimizacija algoritmov za generiranje in risanje dreves, ki smo ga rešili z risanjem na teksturo in omejevanjem števila risalnih klicev. Ugotovili smo, da je pomembno, da je uporabniški vmesnik intuitiven, saj so uporabniki na začetku težko razumeli osnovne funkcije, kot je ocenjevanje z vlečenjem slike.
Za izračun podobnosti med drevesi smo izbrali Hammingovo razdaljo, saj je nudila enostavno implementacijo in jasne rezultate. Objava aplikacije na Google Play Store je zahtevala prilagoditve opisa in politike zasebnosti, da smo ustrezali zahtevam platforme. Za merjenje podobnosti med drevesi smo izbrali Hammingovo razdaljo, ker se računa neposredno nad genomom, pokrije celoten razpon od 0 % do 100 % in je dovolj hitra za izračun ob vsaki novi generaciji. Pri tem smo ugotovili, da ta količina ne meri všečnosti, ampak konvergenco populacije, torej to, kako zelo se je uporabnikova izbira zožila. Prav zato jo je mogoče uporabiti tudi kot diagnostiko postopka: iz poteka krivulje je razvidno, katera veja tvorjenja nove generacije se je izvedla in kako močno je vplivala na populacijo. Objava aplikacije na Google Play Store je zahtevala prilagoditve opisa in politike zasebnosti, da smo ustrezali zahtevam platforme.
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 uspešno dosegel svoje cilje: uporabnikom je omogočil ustvarjanje in prilagajanje fraktalnih dreves, hkrati pa nam je prinesel dragocene izkušnje na področju razvoja, optimizacije in uporabniškega izkušenj. V prihodnosti bi lahko aplikacijo izboljšali z dodajanjem zgodovine in izboljšavo komunikacije podobnosti.
#pagebreak() #pagebreak()
#bibliography("citations.bib", title: [VIRI IN LITERATURA], style: "ieee.csl") #bibliography("citations.bib", title: [VIRI IN LITERATURA], style: "ieee.csl")