SzIProjekt/route-planning.md

5.5 KiB

Sztuczna inteligencja - projekt zespołowy - Autonomiczny Traktor

autorzy: Aleksandra Werda, Natalia Wiśniewska, Kinga Jagodzińska, Aleksandra Jonas


Planowanie ruchu

  • schemat procedury przeszukiwania grafu stanów z uwzględnieniem kosztu
  • strategia A*

Drugim zadaniem dotyczącym projektu jest zastosowanie strategii przeszukiwania przestrzeni stanów do problemu planowania ruchu agenta na kracie.

Heurystyka

  • założenie — odległość pomiędzy sąsiadującymi polami wynosi 2, tyle samo co koszt wjazdu na puste pole
  • s — pole, na którym jesteśmy
  • f — pole końcowe
  • dla s = f program kończy pracę, bo znajdujemy się już w położeniu końcowym, dlatego pominęłyśmy ten przypadek w definicji heurystyki.
def hscore(self, s, f):
        if f > s:
            a_h = (f - s) // 5
        else:
            a_h = (s - f) // 5
        if f % 5 >= s % 5:
            b_h = f % 5 - s % 5
        else:
            b_h = s % 5 - f % 5 + 1
        return 2 * m.sqrt(a_h * 2 + b_h * 2)

Wpierw obliczamy wysokość trójkąta, jaki tworzą obecne i końcowe pole, w celu wyznaczenia przeciwprostokątnej — odległość między s i f.

Funkcja następnika

Tworzymy listę, do której będziemy kolejno generować sąsiadów dla danego x.

def neighbours(self):
        self.neighbours = list(range(25))
        self.neighbours[0] = [1, 5]
        self.neighbours[4] = [3, 9]
        self.neighbours[20] = [15, 21]
        self.neighbours[24] = [19, 23]
        for x in range(1, 4):
            self.neighbours[x] = [x - 1, x + 5, x + 1]
        for x in range(5, 16, 5):
            self.neighbours[x] = [x - 5, x + 1, x + 5]
        for x in range(9, 20, 5):
            self.neighbours[x] = [x - 5, x - 1, x + 5]
        for x in range(21, 24):
            self.neighbours[x] = [x - 1, x - 5, x + 1]
        for x in [6, 7, 8, 11, 12, 13, 16, 17, 18]:
            self.neighbours[x] = [x - 5, x - 1, x + 1, x + 5]

Główna pętla strategii przeszukiwania

Zaczynamy od znalezienia w open_set pola o najniższym f.

  while open_set:
            # Szukanie pola w open_set z najniższym f
            temp1 = max(self.f_score) + 1
            x = 0
            for i in range(len(open_set)):
                if self.f_score[open_set[i]] <= temp1:
                    x = open_set[i]
                    temp1 = self.f_score[open_set[i]]

Jeżeli to pole odpowiada temu, do którego chcemy dojść — wywołujemy funkcję tworzącą ścieżkę z pola wyjściowego. Póki nie — pomijamy.

            if x == koniec:
                return self.reconstruct_path(self.came_from, koniec)

Usuwamy x z open_set i wrzucamy do closed_set, aby upewnić się, że nie będziemy go ponownie sprawdzać.

            open_set.remove(x)
            closed_set.append(x)

Następnie sprawdzamy sąsiadów:

            for y in self.neighbours[x]:

To tutaj chcieliśmy mieć pewność, że nie trafimy ponownie na x.

                if y in closed_set:
                    continue

Chcemy również sprawdzić, czy jeżeli y jest już w open_set, to nie istnieje krótsze przejście z pola startowego przez inne pole na y, niż bezpośrednio na pole y.

                tentative_g_score = self.g_score[x] + self.game.fields[y][3]
                if y not in open_set:
                    open_set.append(y)
                    tentative_is_better = True
                elif tentative_g_score < self.g_score[y]:
                    tentative_is_better = True

Wyliczamy g i f — przewidywany dystans od startu do celu, dla y.

                if tentative_is_better == True:
                    self.came_from[y] = x
                    self.g_score[y] = tentative_g_score
                    self.f_score[y] = self.g_score[y] + self.hscore(y, koniec)

Tworzenie ścieżki

Przechodzimy po tablicy poprzedników, aż dojdziemy do początku — pola, które nie ma poprzednika.

def reconstruct_path(self, came_from, current):
    total_path = [current]
    while came_from[current] != 0:
        current = came_from[current]
        total_path.insert(0, current)
    return total_path

Koszt podróży na pola uprawne

Zmiany w projekcie zaszły również w pliku run.py, gdzie generujemy losowo pola — każdy kolor odpowiada innej uprawie i etapie jej wzrostu.

Mamy tutaj tablicę tablic. Każda z nich zawiera kolejno informacje o: rodzaju upraw, glebie, nawodnieniu pola oraz koszcie podróży na pole z daną uprawą.

def randomize_field(self):
        for x in range(25):
            temp = []
            # nasiona
            temp.append(random.choice(["żyto", "jęczmień", "owies", "marchew", "rzodkiew", "pietruszka", "puste"]))
            # gleba
            temp.append(random.choice([True, False]))
            # woda
            temp.append(random.choice([True, False]))
            # # growth rate
            # temp.append(random)
            # # cost
            if temp[0] == "żyto":
                temp.append(10)
            elif temp[0] == "jęczmień":
                temp.append(12)
            elif temp[0] == "owies":
                temp.append(8)
            elif temp[0] == "marchew":
                temp.append(14)
            elif temp[0] == "rzodkiew":
                temp.append(7)
            elif temp[0] == "pietruszka":
                temp.append(6)
            elif temp[0] == "puste":
                temp.append(2)
            else:
                temp.append(0)

            self.fields.append(temp)