diff --git a/README.md b/README.md index 85f6f6a..f998112 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Sovellus on laadittu Helsingin yliopiston syksyn 2022 kurssille [Aineopintojen h * [Määrittelydokumentti](https://github.com/valtterikantanen/tiralabra/blob/master/dokumentaatio/maarittelydokumentti.md) * [Testausdokumentti](https://github.com/valtterikantanen/tiralabra/blob/master/dokumentaatio/testausdokumentti.md) * [Toteutusdokumentti](https://github.com/valtterikantanen/tiralabra/blob/master/dokumentaatio/toteutusdokumentti.md) +* [Käyttöohje](https://github.com/valtterikantanen/tiralabra/blob/master/dokumentaatio/kayttoohje.md) ### Viikkoraportit @@ -58,4 +59,4 @@ $ poetry run invoke lint ## Huomautuksia -Ohjelman testaukseen käytetyt [kartat ja testireitit](https://github.com/valtterikantanen/tiralabra/blob/master/src/maps) on ladattu [Moving AI Labin](https://www.movingai.com/benchmarks/index.html) kotisivuilta. Kartat on lisensoitu [Open Data Commons Attribution License](https://opendatacommons.org/licenses/by/1-0/) -lisenssillä. \ No newline at end of file +Ohjelman testaukseen käytettävistä kartoista [yksi](https://github.com/valtterikantanen/tiralabra/blob/master/src/maps/Berlin_0_256.map) on ladattu [Moving AI Labin](https://www.movingai.com/benchmarks/index.html) kotisivuilta. Samalla on ladattu myös karttaan liittyvät [testiskenaariot](https://github.com/valtterikantanen/tiralabra/blob/master/src/maps/Berlin_0_256.map.scen). Tiedostot on lisensoitu [Open Data Commons Attribution License](https://opendatacommons.org/licenses/by/1-0/) -lisenssillä. \ No newline at end of file diff --git a/dokumentaatio/kuvat/map_1.png b/dokumentaatio/kuvat/map_1.png new file mode 100644 index 0000000..bb45de6 Binary files /dev/null and b/dokumentaatio/kuvat/map_1.png differ diff --git a/dokumentaatio/kuvat/map_2.png b/dokumentaatio/kuvat/map_2.png new file mode 100644 index 0000000..2cb17b3 Binary files /dev/null and b/dokumentaatio/kuvat/map_2.png differ diff --git a/dokumentaatio/kuvat/map_3.png b/dokumentaatio/kuvat/map_3.png new file mode 100644 index 0000000..4c73c2f Binary files /dev/null and b/dokumentaatio/kuvat/map_3.png differ diff --git a/dokumentaatio/kuvat/map_4.png b/dokumentaatio/kuvat/map_4.png new file mode 100644 index 0000000..0947761 Binary files /dev/null and b/dokumentaatio/kuvat/map_4.png differ diff --git a/dokumentaatio/kuvat/testikattavuus.png b/dokumentaatio/kuvat/testikattavuus.png index 5f10c94..077124b 100644 Binary files a/dokumentaatio/kuvat/testikattavuus.png and b/dokumentaatio/kuvat/testikattavuus.png differ diff --git a/dokumentaatio/maarittelydokumentti.md b/dokumentaatio/maarittelydokumentti.md index 53ad9f4..f670bd3 100644 --- a/dokumentaatio/maarittelydokumentti.md +++ b/dokumentaatio/maarittelydokumentti.md @@ -20,6 +20,6 @@ Torikka, Oskari: [_A*-algoritmi ja siihen pohjautuvat muistirajoitetut heuristis Laaksonen, Antti: [_Tietorakenteet ja algoritmit_](https://raw.githubusercontent.com/hy-tira/tirakirja/master/tirakirja.pdf). -Kord, Richard ym.: [*Time complexity of iterative-deepening-A\**](https://www.sciencedirect.com/science/article/pii/S0004370201000947/pdf?md5=6685f651efae7e0f243de5d7d4522aba&pid=1-s2.0-S0004370201000947-main.pdf). +Korf, Richard ym.: *Time complexity of iterative-deepening-A\**. Artificial Intelligence, 129 (1–2): 199–218. 2001. DOI: [10.1016/S0004-3702(01)00094-7](https://doi.org/10.1016/S0004-3702(01)00094-7). [*Iterative deepening A\**](https://en.wikipedia.org/wiki/Iterative_deepening_A*). diff --git a/dokumentaatio/testausdokumentti.md b/dokumentaatio/testausdokumentti.md index 1dd8c5e..1e4b8d9 100644 --- a/dokumentaatio/testausdokumentti.md +++ b/dokumentaatio/testausdokumentti.md @@ -2,26 +2,103 @@ ## Yksikkötestaus Yksikkötestit voidaan suorittaa helposti käyttämällä [Pytest-sovelluskehystä](https://docs.pytest.org/en/7.1.x/). Testitiedostot sijaitsevat hakemistossa [`src/tests/`](https://github.com/valtterikantanen/tiralabra/blob/master/src/tests/). Yksikkötestit voi suorittaa projektin juurihakemistossa komennolla -```bash +``` poetry run invoke test ``` ja testikattavuusraportin voi generoida komennolla -```bash +``` poetry run invoke coverage-report ``` -Tähän mennessä testausta on tehty: -* [Heap](https://github.com/valtterikantanen/tiralabra/blob/master/src/heap.py/)-luokalle, joka toteuttaa Dijkstran algoritmissa vaadittavan minimikeon -* [dijkstra.py](https://github.com/valtterikantanen/tiralabra/blob/master/src/dijkstra.py/)-tiedostolle, joka sisältää toteutuksen Dijkstran algoritmille -* [ida_star.py](https://github.com/valtterikantanen/tiralabra/blob/master/src/ida_star.py/)-tiedostolle, joka sisältää toteutuksen IDA\*-algoritmille +Yksikkötestausta on tehty: +* [Heap](https://github.com/valtterikantanen/tiralabra/blob/master/src/util/heap.py/)-luokalle, joka toteuttaa Dijkstran algoritmissa vaadittavan minimikeon +* [graph.py](https://github.com/valtterikantanen/tiralabra/blob/master/src/util/graph.py/)-tiedostolle, joka sisältää tarvittavat toiminnot tekstimuotoisten karttojen muuttamiseksi vieruslistamuotoon +* [dijkstra.py](https://github.com/valtterikantanen/tiralabra/blob/master/src/algorithms/dijkstra.py/)-tiedostolle, joka sisältää toteutuksen Dijkstran algoritmille +* [ida_star.py](https://github.com/valtterikantanen/tiralabra/blob/master/src/algorithms/ida_star.py/)-tiedostolle, joka sisältää toteutuksen IDA\*-algoritmille +* [ui_logic.py](https://github.com/valtterikantanen/tiralabra/blob/master/src/services/ui_logic.py/)-tiedostolle, joka sisältää käyttöliittymäkoodin ### Testikattavuus -Sovelluksen testikattavuus näyttää tällä hetkellä tältä: +Sovelluksen testikattavuus näyttää seuraavalta: ![](./kuvat/testikattavuus.png) Sovelluksen testauksen haaraumakattavuus on näin ollen 99 %. Testikattavuuden ulkopuolelle on jätetty käyttöliittymäkoodin sisältävä hakemisto `src/ui/` ja testaukseen käytettävän koodin sisältävä hakemisto `src/tests/` sekä tiedostot `__init__.py` ja `index.py`. -### Dijkstran algoritmin toiminnallisuuden testaus -Dijkstran algoritmin testaus on toteutettu niin, että esimerkkikarttojen yhteydessä samalta sivustolta on ladattu [esimerkkiskenaarioita](https://github.com/valtterikantanen/tiralabra/blob/master/src/maps/Berlin_0_256.map.scen), joista ilmenee 930 eri pisteparin väliset etäisyydet. Testauksen yhteydessä näistä valitaan satunnaisesti viisitoista, ja ohjelman antamaa tulosta verrataan tiedettyyn etäisyyteen. Testi on asetettu menemään läpi, jos etäisyydet ovat kuuden desimaalin tarkkuudella samat. +## Suorituskykytestaus + +Alla olevissa vertailuissa on arvottu tietty määrä satunnaisia reittejä, jotka ovat olleet samoja molemmille algoritmeille. + +### Testien toistaminen + +Testit voidaan toistaa projektin juurihakemistossa komennolla +``` +poetry run invoke perf-test +``` +Testien tulokset tulostetaan terminaaliin sitä mukaa kuin ne valmistuvat. Jos toistomääriä haluaa muokata, se onnistuu päivittämällä [main_perf_test.py-tiedostoa](../src/tests/performance_tests/main_perf_test.py). + +### Tyhjä 10×10-kokoinen kartta +![](./kuvat/map_1.png) + +Kartassa tutkittiin 50 kappaletta 10 000 reitin sarjoja. Alla olevan taulukon ajat ovat siis 10 000 reitin löytämiseen käytettyjä aikoja. + +| Algoritmi | Nopein | Hitain | Keskiarvo | Mediaani | +| --------- | ----------- | ----------- | ----------- | ----------- | +| Dijkstra | 0,0132916 s | 0,0176356 s | 0,0155179 s | 0,0155268 s | +| IDA\* | 0,0013673 s | 0,0019615 s | 0,0016281 s | 0,0016195 s | + +Ajoista huomataan, että IDA\* oli kaikissa tilanteissa hitaimmillaankin yli 5 kertaa Dijkstraa nopeampi. Molemmilla algoritmeilla nopeimpaan ja hitaimpaan sarjaan käytetyt ajat eroavat suhteellisen vähän toisistaan: hitaimpaan sarjaan kului 33–43 % enemmän aikaa. + +### Vain vähän esteitä sisältävä 10×10-kokoinen kartta +![](./kuvat/map_2.png) + +Kartassa tutkittiin 50 kappaletta 10 000 reitin sarjoja. Alla olevan taulukon ajat ovat siis 10 000 reitin löytämiseen käytettyjä aikoja. + +| Algoritmi | Nopein | Hitain | Keskiarvo | Mediaani | +| --------- | ----------- | ----------- | ----------- | ----------- | +| Dijkstra | 0,0095181 s | 0,0129201 s | 0,0115847 s | 0,0115386 s | +| IDA\* | 0,0030134 s | 0,0099306 s | 0,0056182 s | 0,0051652 s | + +Ajoista huomataan, että IDA\* oli keskimäärin noin 52 % nopeampi kuin Dijkstra. Dijkstran algoritmi oli kaikissa tilanteissa nopeampi kuin ensimmäisessä testissä, mikä selittynee sillä, että esteiden takia kaaria on vähemmän ja näin ollen ne voidaan käydä nopeammin läpi. + +Dijkstran algoritmilla nopeimpaan ja hitaimpaan sarjaan käytetyt ajat eroavat edelleen suhteellisen vähän toisistaan: hitaimpaan sarjaan kului 36 % enemmän aikaa. Sen sijaan IDA\*-algoritmissa suoritusajat alkoivat vaihdella enemmän: hitain sarja vei noin 230 % enemmän aikaa. + +### Vain vähän esteitä sisältävä 20×20-kokoinen kartta +![](./kuvat/map_3.png) + +Kartassa tutkittiin 20 000 reittiä. Alla olevan taulukon ajat ovat yhden reitin löytämiseen käytettyjä aikoja. + +| Algoritmi | Nopein | Hitain | Keskiarvo | Mediaani | +| --------- | ----------- | ------------ | ----------- | ----------- | +| Dijkstra | 0,0000253 s | 0,0023105 s | 0,0006759 s | 0,0006729 s | +| IDA\* | 0,0000024 s | 14,5572371 s | 0,0041142 s | 0,0000417 s | + +IDA\*-algoritmilla tutkituista reiteistä meni eniten aikaa ruudusta (2, 0) ruutuun (17, 19) kulkevan reitin löytämiseen. Pisteet on merkitty kuvaan vihreällä. + +Dijkstran algoritmi oli jälleen kaikissa tilanteissa nopeampi kuin edellisessä testissä, vaikka kartta onkin suurempi. + +Kun tarkastellaan mediaanitapausta, IDA\*-algoritmilla suoritus kesti vain noin 6 % siitä, mitä Dijkstran algoritmilla. Keskimäärin IDA\* on siis edelleen nopea. Dijkstran algoritmilla hitain tapaus vei yli 90 kertaa enemmän aikaa kuin nopein tapaus, ja IDA\*-algoritmilla hitain tapaus vei yli 6 miljoonaa kertaa enemmän aikaa kuin nopein tapaus. Algoritmin valinnalla voidaan siis optimoida joko nopeinta keskimääräistä suoritusaikaa tai varmuutta siitä, että hitainkin tapaus vie korkeintaan sekunnin tuhannesosia. + +### Kohtalaisesti esteitä sisältävä 20×20-kokoinen kartta +![](./kuvat/map_4.png) + +Kartassa tutkittiin 20 000 reittiä. Alla olevan taulukon ajat ovat yhden reitin löytämiseen käytettyjä aikoja. + +| Algoritmi | Nopein | Hitain | Keskiarvo | Mediaani | +| --------- | ----------- | ------------- | ----------- | ----------- | +| Dijkstra | 0,0000255 s | 0,0022547 s | 0,0005928 s | 0,0005982 s | +| IDA\* | 0,0000024 s | 324,7890677 s | 0,1860766 s | 0,0000639 s | + +IDA\*-algoritmilla tutkituista reiteistä meni eniten aikaa ruudusta (19, 1) ruutuun (1, 15) kulkevan reitin löytämiseen. Pisteet on merkitty kuvaan vihreällä. + +Dijkstran algoritmi oli keskimäärin hieman nopeampi kuin edellisessä kartassa. Vaikuttaa siis siltä, että esteiden määrän kasvattaminen tässäkin tapauksessa nopeuttaa reitin löytämistä. + +Mediaanitapauksessa IDA\*-algoritmilla suoritus kesti vain noin 11 % siitä, mitä Dijkstran algoritmilla. Keskimäärin IDA\* on siis edelleen nopea. Dijkstran algoritmilla hitain tapaus vei jälleen noin 88 kertaa enemmän aikaa kuin nopein tapaus, ja IDA\*-algoritmilla hitain tapaus vei yli 135 miljoonaa kertaa enemmän aikaa kuin nopein tapaus. Algoritmin valinnalla voidaan siis optimoida joko nopeinta keskimääräistä suoritusaikaa tai varmuutta siitä, että hitainkin tapaus vie korkeintaan sekunnin tuhannesosia. + +### Muu suorituskykytestaus + +Dijkstran algoritmin toimintaa testattiin myös 930 eri syöttellä 256×256-kokoisessa kartassa. Kaikissa tapauksissa ohjelma löysi oikean lyhimmän reitin. Testaus tehtiin [esimerkkiskenaarioiden](https://github.com/valtterikantanen/tiralabra/blob/master/src/maps/Berlin_0_256.map.scen) avulla. 930 eri testitapauksen ajo kesti 168 sekuntia eli yhdessä tapauksessa kului aikaa keskimäärin 0,180 sekuntia. Nämä testit voidaan toistaa projektin juurihakemistossa komennolla +``` +poetry run invoke dijkstra-test-all +``` +Testauksen yhteydessä tämän projektin antamaa tulosta verrataan skenaariotiedostossa mainittuun etäisyyteen. Testi on asetettu menemään läpi, jos etäisyydet ovat kuuden desimaalin tarkkuudella samat. \ No newline at end of file diff --git a/dokumentaatio/toteutusdokumentti.md b/dokumentaatio/toteutusdokumentti.md index 96b7320..726af08 100644 --- a/dokumentaatio/toteutusdokumentti.md +++ b/dokumentaatio/toteutusdokumentti.md @@ -5,23 +5,35 @@ Projektin rakenne on seuraava: ``` src -├── dijkstra.py -├── graph.py -├── heap.py -├── ida_star.py +├── algorithms +│   ├── dijkstra.py +│   └── ida_star.py ├── index.py ├── maps -│ ├── Berlin_0_256.map -│ ├── Berlin_0_256.map.scen -│ └── Map_20.map +│   ├── Berlin_0_256.map +│   ├── Berlin_0_256.map.scen +│   ├── Map_20.map +│   ├── Map_5.map +│   └── perf_test_maps +│      ├── map1.map +│      ├── map2.map +│      ├── map3.map +│ └── map4.map +├── services +│   └── ui_logic.py ├── tests -│ ├── dijkstra_test.py -│ ├── heap_test.py -│ ├── ida_star_test.py -│ └── performance_tests -│ └── dijkstra_performance_test.py -└── ui - └── ui.py +│   ├── dijkstra_test.py +│   ├── heap_test.py +│   ├── ida_star_test.py +│   ├── ui_logic_test.py +│   └── performance_tests +│      ├── dijkstra_test_all_cases.py +│ └── main_perf_test.py +├── ui +│   └── ui.py +└── util + ├── graph.py + └── heap.py ``` Osien toiminta on pääasiassa seuraavanlainen: @@ -29,18 +41,30 @@ Osien toiminta on pääasiassa seuraavanlainen: * [`dijkstra.py`](https://github.com/valtterikantanen/tiralabra/blob/master/src/dijkstra.py) ja [`ida_star.py`](https://github.com/valtterikantanen/tiralabra/blob/master/src/ida_star.py) vastaavat Dijkstran algoritmin ja IDA\*-algoritmin toteutuksista * [`graph.py`](https://github.com/valtterikantanen/tiralabra/blob/master/src/graph.py) vastaa tekstimuotoisten karttojen muuttamisesta verkoksi vieruslistaesitystä käyttäen * [`heap.py`](https://github.com/valtterikantanen/tiralabra/blob/master/src/heap.py) vastaa Dijkstran algoritmissa tarvittavan binäärikeon toteutuksesta +* [`ui_logic.py`](https://github.com/valtterikantanen/tiralabra/blob/master/src/services/ui_logic.py) vastaa käyttöliittymälogiikasta * [`index.py`](https://github.com/valtterikantanen/tiralabra/blob/master/src/index.py) käynnistää ohjelman toiminnan -* [`/maps`](https://github.com/valtterikantanen/tiralabra/tree/master/src/maps) sisältää esimerkkikarttoja -* [`/tests`](https://github.com/valtterikantanen/tiralabra/tree/master/src/tests) sisältää ohjelmalle laaditut testitiedostot +* [`/maps/`](https://github.com/valtterikantanen/tiralabra/tree/master/src/maps) sisältää esimerkkikarttoja +* [`/tests/`](https://github.com/valtterikantanen/tiralabra/tree/master/src/tests) sisältää ohjelmalle laaditut testitiedostot (alikansio [`/performance_tests/`](https://github.com/valtterikantanen/tiralabra/tree/master/src/tests/performance_tests) sisältää suorituskykyä mittaavia testejä) * [`/ui/ui.py`](https://github.com/valtterikantanen/tiralabra/tree/master/src/ui/ui.py) sisältää käyttöliittymäkoodin ## Yleistä ohjelman käytöstä -Asennuksen jälkeen ohjelma käynnistetään komennolla `poetry run invoke start`. Graafisessa käyttöliittymässä ilmoitetaan lähtö- ja maaliruutujen koordinaatit pilkulla erotettuna (esim. `0,0`). Tämän jälkeen valitaan käytettävä algoritmi ja painetaan "Löydä lyhin reitti", jolloin ohjelma näyttää laskentaan käytetyn ajan ja löydetyn reitin pituuden. Ohjelma myös piirtää reitin karttaan. Alussa ruudut ovat joko valkoisia (vapaita) tai mustia (esteitä). Reitin löydyttyä lähtö- ja maaliruudut merkitään vihreällä, muut reittiin kuuluvat ruudut punaisella ja muut vieraillut ruudut keltaisella. +Asennuksen jälkeen ohjelma käynnistetään komennolla `poetry run invoke start`. Graafisessa käyttöliittymässä ilmoitetaan lähtö- ja maaliruutujen koordinaatit pilkulla erotettuna (esim. `0, 0`). Tämän jälkeen valitaan käytettävä algoritmi ja painetaan "Löydä lyhin reitti", jolloin ohjelma näyttää laskentaan käytetyn ajan ja löydetyn reitin pituuden. Ohjelma myös piirtää reitin karttaan. Alussa ruudut ovat joko valkoisia (vapaita) tai mustia (esteitä). Reitin löydyttyä lähtö- ja maaliruudut merkitään vihreällä, muut reittiin kuuluvat ruudut punaisella ja muut vieraillut ruudut keltaisella. + +Yksityiskohtaisemmat ohjeet ovat [käyttöohjeessa](https://github.com/valtterikantanen/tiralabra/blob/master/dokumentaatio/kayttoohje.md). ## Tietorakenteet ja algoritmit +### Binäärikeko +Keko on toteutettu minimikekona, joka mahdollistaa pienimmän alkion löytymisen nopeasti. Kun kekoon lisätään tai siitä poistetaan alkio, palautetaan voimaan niin sanottu kekoehto. Kekoehdon mukaan minimikeossa solmun lapsien täytyy olla vähintään yhtä suuret kuin vanhempansa. Esimerkiksi solmulla, jonka arvo on 8, voi olla lapset arvoltaan 8 ja 10, mutta ei lapsia, joiden arvot ovat 7 ja 5. + +Binäärikeko on toteutettu binääripuuna, ja pohjimmiltaan se on tallennettu tavallisena listana. Pienin alkio on aina indeksissä 1 (indeksi 0 on tyhjä). Yleisesti, jos alkio on indeksissä $k$, niin sen lapsien indeksit ovat $2k$ (vasen lapsi) ja $2k+1$ (oikea lapsi) ja vanhempi sijaitsee indeksissä $\lfloor k/2 \rfloor$. + +Uusi alkio lisätään aluksi seuraavaan vapaana olevaan paikkaan. Tämän jälkeen alkiota nostetaan ylöspäin, kunnes kekoehto on jälleen voimassa. Vastaavasti kun pienin alkio poistetaan keon juuresta, siirretään aluksi keon viimeinen alkio uudeksi juureksi. Tämän jälkeen alkiota lasketaan alaspäin, kunnes kekoehto on jälleen voimassa. + +Alkion lisäämisen ja poistamisen lisäksi keossa on metodi keon koon selvittämiselle sekä metodi, joka kertoo, onko keko tyhjä vai ei. + ### Dijkstran algoritmi -Dijkstran algoritmi on toteutettu yhdessä funktiossa, joka saa parametrinaan käytettävän verkon vieruslistaesityksenä, lähtösolmun ja maalisolmun. Aluksi alustetaan kaksi listaa, joista toinen pitää kirjaa vierailluista solmuista ja toinen tietoa siitä, mistä solmusta mihinkin solmuun on päästy. Tätä tietoa tarvitaan polun muodostamisessa. Lisäksi luodaan keko, johon lisätään alussa vain lähtösolmu, ja sanakirja, jossa ylläpidetään tietoa etäisyyksistä. +Dijkstran algoritmi on toteutettu yhdessä funktiossa, joka saa parametreina käytettävän verkon vieruslistaesityksenä, lähtösolmun ja maalisolmun. Aluksi alustetaan kaksi listaa, joista toinen pitää kirjaa vierailluista solmuista ja toinen tietoa siitä, mistä solmusta mihinkin solmuun on päästy. Tätä tietoa tarvitaan polun muodostamisessa. Lisäksi luodaan keko, johon lisätään alussa vain lähtösolmu, ja sanakirja, jossa ylläpidetään tietoa etäisyyksistä. While-silmukkaa toistetaan kunnes keko on tyhjä tai maalisolmu löydetään. Näin voidaan toimia, sillä algoritmi toimii niin, että kun maalisolmu löydetään, siihen ei voi löytyä enää lyhyempiä reittejä. Näin on siksi, että keosta poistetaan aina solmu, johon on lähtösolmusta lyhin etäisyys. Jos kauempaa kiertämällä voisi löytyä lyhyempi etäisyys, jossakin kohdassa olisi oltava negatiivinen solmun paino, mikä ei ole Dijkstran algoritmissa sallittua. @@ -49,13 +73,55 @@ Joka kierroksella keosta poistetaan siis solmu, jonka naapurit käydään vuorot Lopuksi muodostetaan vielä lista reitin muodostavista solmuista. Funktio palauttaa reitin, tiedon vierailluista solmuista sekä etäisyyden maalisolmuun. ### IDA\*-algoritmi +IDA\*-algoritmin toiminta on jaettu kahteen funktioon: `ida_star`-pääfunktioon ja rekursiiviseen `_search`-funktioon. Pääfunktio saa parametreina käytettävän verkon vieruslistaesityksenä, lähtösolmun ja maalisolmun. Rekursiivisen funktion parametreina on käytettävä verkko, polun nykyinen pituus (g-arvo), hakuraja eli tutkittavien polkujen maksimipituus, etsittävä solmu sekä tutkittava polku. -### Binäärikeko +Aluksi tarkistetaan, onko joko lähtö- tai maalisolmussa este. Jos on, funktiosta palataan välittömästi, koska mitään reittiä ei voi löytyä. Varsinaisen algoritmin toiminta alkaa lyhimmän mahdollisen reitin pituuden määrittämisellä, missä käytetään apuna `_estimate_shortest_path`-funktiota. Funktio laskee lyhimmän mahdollisen etäisyyden seuraavasti: +* Ensin liikutaan mahdollisimman paljon viistoon, kunnes ollaan samalla vaaka- tai pystyakselilla kuin maaliruutu (yhden siirtymän pituus on $\sqrt{2}$) +* Tämän jälkeen loppuosa matkasta on ainoastaan vaaka- tai pystysuuntaisia siirtymiä (yhden siirtymän pituus on 1) -## Aika- ja tilavaativuudet +Algoritmin kannalta on tärkeää, että heuristinen arvio on määritelty oikein (eli heuristiikka on luvallinen). Toisin sanoen arvio ei koskaan saa olla yläkanttiin, koska muuten lyhintä reittiä ei välttämättä löydetä. + +Reitti alustetaan niin, että siihen asetetaan aloitussolmu. Tämän jälkeen rekursiivista funktiota kutsutaan uudelleen niin kauan, kunnes maalisolmu on löytynyt tai on todettu, ettei reittiä ylipäänsä voi löytyä. Muussa tapauksessa tutkittavien polkujen maksimipituutta kasvatetaan joka kierroksella. Uudeksi hakurajaksi tulee kaikista löydetyistä reiteistä lyhimmän pituus (joka on kuitenkin suurempi kuin edellisen kierroksen raja). + +Rekursiivinen funktio toimii niin, että polun viimeisestä solmusta lähtien aletaan tarkastella mahdollisia reittiä. Uusi arvio aloitus- ja maalisolmun etäisyydestä (f-arvo) lasketaan lisäämällä polun nykyiseen pituuteen (g-arvo) heuristinen arvio nykyisen solmun ja maalisolmun välisestä etäisyydestä. Jos näin saatu arvo on suurempi, kuin kullakin hetkellä voimassa oleva raja, palautetaan f-arvo. Vastaavasti jos ollaan saavutettu maalisolmu, tiedetään, että ollaan löydetty lyhin reitti, joten tässäkin tapauksessa voidaan palata pääfunktioon. + +Muussa tapauksessa aletaan selvittää, mikä olisi lyhimmän reitin pituus, joka on kuitenkin pidempi kuin tämänhetkinen raja ja johon päästään nykyisestä solmusta. Tämä tapahtuu käymällä nykyisen solmun naapurit läpi, ja tutkimalla rekursiivisesti syvyyshaulla ne solmut, jotka eivät tällä hetkellä kuulu reittiin. Kun vaihtoehdot on tutkittu, palautetaan lyhimmän löydetyn reitin pituus. + +## Saavutetut aika- ja tilavaativuudet + +### Keko +Binääripuun rakenne mahdollista pienimmän alkion löytymisen ajassa $O(1)$, koska pienin alkio on aina keon juuressa. Alkion poistaminen ja lisääminen tapahtuvat ajassa $O(\log n)$, missä $n$ on keon alkioiden määrä. Tällöin tasojen määrä keossa on $O(\log n)$, ja pahimmassa tapauksessa lisäyksen tai poiston yhteydessä alkio joudutaan siirtämään ylimmältä tasolta alimmalle tai toisin päin. + +### Dijkstran algoritmi +Algoritmi käy läpi verkon solmut ja kaaret, mihin kuluu aikaa $O(|E| + |V|)$, missä $|E|$ on kaarten lukumäärä ja $|V|$ solmujen lukumäärä. Pahimmassa tapauksessa jokainen kaari joudutaan lisäämään vuorollaan kekoon, mihin kuluu aikaa $O(|E| \log |E|)$, sillä yksi lisäys vie aikaa $O(\log |E|)$. Vastaavasti pahimmassa tapauksessa reitin löytämiseksi joudutaan mahdollisesti poistamaan keosta jokainen alkio, mihin kuluu aikaa myös $O(|E| \log |E|)$. Tästä saadaan aikavaativuus $O(|E| + |V|) + O(|E| \log |E|) + O(|E| \log |E|) = O(|E| + |V|) + O(2 |E| \log 2 |E|) = O(|E| \log |E| + |V|)$. + +Pahimmassa tapauksessa kekoon on talletettu samanaikaisesti kaikki kaaret. Toteutuksessa solmusta voi lähteä enintään kahdeksan kaarta, joten pahimman tapauksen tilavaativuudeksi saadaan $O(8 |V|) = O(|V|)$, missä $|V|$ on solmujen lukumäärä. + +### IDA\*-algoritmi +Algoritmin aikavaativuus riippuu ongelmasta sekä siitä, kuinka hyvin heuristiikkafunktio toimii kyseiseen ongelmaan. Yleisesti ottaen pahimman tapauksen aikavaativuus on $O(b^d)$, missä $b$ on niin sanottu haarautumiskerroin (*branching factor*) ja $d$ lyhimpään reittiin kuuluvien solmujen määrä. Tässä projektissa haarautumiskerroin on yleisesti ottaen kahdeksan, koska jokaisesta solmusta lähtee enintään kahdeksan kaarta. + +Näin ollen aikavaativuus on eksponentiaalinen polun pituuteen nähden. Mitä lähempänä heuristinen arvio on todellista lyhimmän reitin pituutta, sitä vähemmän laskentakierroksia tarvitsee tehdä. Esimerkiksi siinä tapauksessa, että heuristiikkafunktio antaa suoraan lyhimmän polun pituuden, riittää yksi kierros. Tästä syystä IDA\* on hyvin nopea silloin, kun lähtö- ja maalisolmun välillä ei ole lainkaan esteitä, koska tällöin toteutettu heuristiikka antaa suoraan lyhimmän polun pituuden. Haasteeksi nouseekin se, miten matkalla mahdolliset olevat esteet voisi huomioida heuristiikassa, jotta kaikista lyhimpiä reittiä ei tarvitsisi tutkia lainkaan. + +Tilavaativuus on parempi kuin Dijkstran algoritmissa, sillä tallennettuna on ainoastaan kullakin hetkellä tutkittava polku. Näin ollen tilavaativuus on lineaarinen suhteessa lyhimmän reitin pituuteen eli $O(n)$, missä $n$ on lyhimpään reittiin kuuluvien solmujen määrä. Tämä kuitenkin johtaa siihen, että samoja solmuja joudutaan käymään läpi useaan kertaan. ## Suorituskykyvertailu -Dijkstran algoritmin toimintaa on testattu 930 eri syöttellä 256×256-kokoisessa kartassa. Kaikissa tapauksissa ohjelma löysi oikean lyhimmän reitin. Testaus tehtiin [esimerkkiskenaarioiden](https://github.com/valtterikantanen/tiralabra/blob/master/src/maps/Berlin_0_256.map.scen) avulla. 930 eri testitapauksen ajo kesti 168 sekuntia eli yhdessä tapauksessa kului aikaa keskimäärin 0,180 sekuntia. + +Tarkemmat tiedot käytetyistä testisyötteistä ja tuloksista ovat [testausdokumentissa](testausdokumentti.md). + +Testeissä huomattiin, että mitä pienempi kartta ja mitä vähemmän esteitä, sitä nopeammin IDA\* löytää lyhimmän reitin. Esimerkiksi tyhjässä 10×10-kokoisessa kartassa ja 10 000 reitin sarjoilla IDA\* oli kaikissa tilanteissa hitaimmillaankin yli 5 kertaa Dijkstraa nopeampi. + +Testeissä myös huomattiin, että Dijkstran algoritmi nopeutui esteiden määrän lisääntyessä. Tämä on ymmärrettävää, koska tällöin verkossa on vähemmän kaaria ja ne voidaan käydä nopeammin läpi. + +Kartan koon ja esteiden määrän kasvaessa Dijkstran algoritmin suorituskyky pysy suhteellisen samana, kun taas IDA\*-algoritmissa hitaimmat tapaukset erottuvat hyvin selvästi. Esimerkiksi vain yksittäisiä esteitä sisältävässä 20×20-kokoisessa kartassa reitin löytäminen vei IDA\*-algoritmilta pahimmillaan yli 14 sekuntia, kun Dijkstran algoritmi selviytyi kaikista tapauksista alle 2,5 millisekunnissa. + +Jos IDA\*-algoritmin heuristiikassa huomioitaisiin jollain tavalla esteiden määrä reitillä, saataisiin myös algoritmin suorituskykyä paremmaksi. Näyttää siltä, että jos kartan koosta tai esteiden määrästä ei ole lisätietoja, on Dijkstran algoritmi yleispätevämpi, vaikka se onkin mediaanitapauksessa hitaampi. + +## Parannusehdotuksia +Yksi selkeä parannus olisi IDA\*-algoritmiin liittyvän heuristiikkafunktion kehittäminen. Tällä hetkellä esimerkiksi esteiden määrää reitillä ei oteta lainkaan huomioon, vaan funktio palauttaa samoille alku- ja päätepisteille aina saman etäisyysarvion. Tämä hidastaa huomattavasti reittien läpikäymistä tilanteissa, joissa esteitä on runsaasti eli arvio ja todellinen lyhin reitti eroavat paljon toisistaan. ## Lähteet -Laaksonen, Antti: [_Tietorakenteet ja algoritmit_](https://raw.githubusercontent.com/hy-tira/tirakirja/master/tirakirja.pdf), s. 123–127. \ No newline at end of file +Korf, Richard ym.: *Time complexity of iterative-deepening-A\**. Artificial Intelligence, 129 (1–2): 199–218. 2001. DOI: [10.1016/S0004-3702(01)00094-7](https://doi.org/10.1016/S0004-3702(01)00094-7). + +Laaksonen, Antti: [_Tietorakenteet ja algoritmit_](https://raw.githubusercontent.com/hy-tira/tirakirja/master/tirakirja.pdf), s. 123–127. + +Myllyoja, Henri: [_Binääri- ja Fibonacci-keot prioriteettijonon toteutukseen_](https://trepo.tuni.fi/bitstream/handle/123456789/25515/myllyoja.pdf?sequence=4&isAllowed=y). Kandidaatintyö. Tampereen teknillinen yliopisto, tieto- ja sähkötekniikan tiedekunta, 2018. \ No newline at end of file