Politechnika Śląska w Gliwicach Wydział: Automatyki, Elektroniki i Informatyki Kierunek: Automatyka i Robotyka, semestr 9 Specjalizacja: Komputerowe Systemy Sterowania

SPRAWOZDANIE Projekt:

PROGRAMOWANIE OGRANICZENIOWE

Sekcja: Kobyłko Rafał Musik Jakub

Gliwice 2015

SPIS TREŚCI 1

Wstęp ........................................................................................................................................................................ 3

2

Technologie ............................................................................................................................................................... 3

3

Problem harmonogramowania ................................................................................................................................. 4 3.1

Zadanie .............................................................................................................................................................. 4

3.2

Model problemu i realizacja ............................................................................................................................. 4

3.2.1

Wykonanie poprzedniego zadania ............................................................................................................ 4

3.2.2

Czas zakończenia poprzedniego zadania .................................................................................................. 4

3.2.3

Minimalizowany wskaźnik......................................................................................................................... 4

3.2.4

Jedno zadanie w danym czasie ................................................................................................................. 5

3.2.5

Nagroda za zadanie ................................................................................................................................... 5

3.2.6

Wymagana nagroda .................................................................................................................................. 5

3.2.7

Odległości między zadaniami .................................................................................................................... 5

3.3

4

Aplikacja ............................................................................................................................................................ 6

3.3.1

Zakładka „Search” ..................................................................................................................................... 6

3.3.2

Zakładka „Quest Settings”......................................................................................................................... 7

3.3.3

Rozwiązane zadanie .................................................................................................................................. 8

Problem optymalizacji............................................................................................................................................... 9 4.1

Zadanie .............................................................................................................................................................. 9

4.2

Realizacja........................................................................................................................................................... 9

4.2.1

Wykorzystana struktura ............................................................................................................................ 9

4.2.2

Realizacja programowa ........................................................................................................................... 10

4.2.3

Ogólna struktura programu .................................................................................................................... 12

4.3

Aplikacja .......................................................................................................................................................... 12

4.3.1

Parametry problemu ............................................................................................................................... 13

4.3.2

Mapa ....................................................................................................................................................... 13

4.3.3

Działanie programu i przykład................................................................................................................. 14

5

Organizacja pracy – Git ........................................................................................................................................... 16

6

Dokumentacja ......................................................................................................................................................... 18

2

1 WSTĘP Celem projektu było zastosowanie programowania ograniczeniowego do rozwiązywania problemów z zakresu harmonogramowania i optymalizacji. Przedstawiono po jednym zadaniu z każdej kategorii i utworzono aplikację, która je rozwiązuje.

2 TECHNOLOGIE W trakcie realizacji projektu korzystano ze środowiska programistycznego IntelliJ IDEA 14. Aplikacja napisana została w języku Java i wykorzystuje JDK w wersji 8u25. Jako wsparcie programowania ograniczeniowego posłużyła biblioteka JaCoP 4.1.0. W kilku miejscach użyto także biblioteki Google Guava, która oferuje szereg bardzo przydatnych funkcji. Warstwa interfejsu użytkownika została zbudowana w oparciu o JavaFX. FX charakteryzuje się tym, że ułatwia projektowanie aplikacji w oparciu o wzorzec MVC (lub MVP), a także oferuje duże możliwości „dekorowania” GUI za pomocą stylów CSS. Jest to rozwiązanie nowsze niż popularny Swing. Jako narzędzie do budowy projektu wykorzystano Gradle. Standardowa struktura aplikacji FX wygląda następująco:     

src/main/java/ – pakiety i pliki źródłowe .java, src/main/resources/fxml – pliki .fxml odpowiedzialne za GUI (generowane przez SceneBuilder), src/main /resoruces/images – grafiki, src/main /resources/styles – pliki CSS, build/ – różne pliki wynikowe generowane przez Gradle (m.in. Javadoc, .jar itp.).

Fragment drzewa projektu:

Oprócz zadań stricte programistycznych, dużą uwagą skupiono na organizacji pracy. W tym celu dogłębnie zapoznano się z rozproszonym systemem kontroli wersji Git. Więcej informacji w kolejnych rozdziałach.

3

3 PROBLEM HARMONOGRAMOWANIA 3.1 ZADANIE Analizowany problem harmonogramowania polega na wybraniu oraz ułożeniu w odpowiedniej kolejności zadań tak, aby jak najszybciej osiągnąć wymaganą sumę nagród za zadania. Z problem tym można się zetknąć, np.: w różnego rodzaju grach komputerowych. Aby bardziej przybliżyć to zagadnienie, sformułowano je nieco inaczej – w sposób „fabularny”: Które questy (pol. zadania) najlepiej wykonać i w jakiej kolejności tak, aby jak najszybciej zdobyć ilość doświadczenia (eng. exp – experience) (nagrody za zadania) pozwalającą na osiągnięcie kolejnego poziomu doświadczenia (eng. level). Założenia:   

Każde zadanie może wymagać wykonania innego, dowolnego zadania przed nim. Każde zadanie ma określony czas trwania. Każde zadanie jest zlokalizowane w przestrzeni dwuwymiarowej, a koszt przejścia pomiędzy nimi jest zależny od odległości.

3.2 MODEL PROBLEMU I REALIZACJA Aby zamodelować problem konieczne było podzielenie go na pomniejsze ograniczenia, a następnie zamodelowanie każdego z nich. 3.2.1 Wykonanie poprzedniego zadania Każde zadanie otrzymało swoją zmienną isDone oznaczającą to, czy jest ono wykonywane czy też nie. Przyjmuje ona wartości 1, jeżeli zadanie jest wykonywane oraz 0 jeżeli nie. Zatem, zależność wymuszająca wykonanie wymaganego zadania:

store.impose(new XlteqY(quest.getIsDone(),previousQuest.getIsDone())); 3.2.2 Czas zakończenia poprzedniego zadania Wymagane zadanie powinno zostać wykonane przed zadaniem, które go wymaga – czyli czas rozpoczęcia i czas trwania wymaganego zadania po zsumowaniu, powinny być mniejsze lub równe czasowi rozpoczęcia zadania, które go wymaga.

PrimitiveConstraint c1 = new XeqC(quest.getIsDone(),1); PrimitiveConstraint c2 = new XplusYlteqZ(previousQuest.getBeginTime(),previousQuest.getDuration(),quest.getB eginTime()); store.impose(new IfThen(c1,c2)); 3.2.3 Minimalizowany wskaźnik Ponieważ czas zakończenia ostatniego zadania powinien być jak najmniejszy, tworzony jest wskaźnik jakości, definiowany jako maksymalna wartość z czasów zakończeń zadań, których isDone = 1.

4

for (Quest quest : questList){ IntVar endTime = new IntVar(store, quest.getQuestName() +"p"+ quest.getQuestPart()+"EndTime", quest.getBeginTime().min(),quest.getBeginTime().max()+quest.getDuration().max() ); IntVar endTimeBound = new IntVar(store, quest.getQuestName() +"p"+ quest.getQuestPart() +"EndTimeBound",quest.getBeginTime().min(),quest.getBeginTime().max()+quest.get Duration().max()); endTimes.add(endTimeBound); store.impose(new XplusYeqZ(quest.getBeginTime(),quest.getDuration(),endTime)); store.impose(new XmulYeqZ(quest.getIsDone(),endTime,endTimeBound)); } IntVar maxEndTime = new IntVar(store,"MaxEndTime",0,data.getMaxTime()*10); store.impose(new Max(endTimes.toArray(new IntVar[endTimes.size()]),maxEndTime));

3.2.4 Jedno zadanie w danym czasie Ponieważ nie jest możliwe wykonywane dwóch zadań w jednym czasie, zastosowano ograniczenia powodujące, że zadania nie mogą na siebie nachodzić, jeżeli oba są wykonywane, tzn. ich isDone = 1.

PrimitiveConstraint c3 = new XplusYeqC(quest.getIsDone(),quest.getIsDone(),2); PrimitiveConstraint c1 = new XplusYlteqZ(quest.getBeginTime(), quest.getDuration(), otherQuest.getBeginTime()); PrimitiveConstraint c2 = new XplusYlteqZ(otherQuest.getBeginTime(), otherQuest.getDuration(), quest.getBeginTime()); store.impose(new IfThen(c3,new Or(c1,c2))); 3.2.5 Nagroda za zadanie Nagroda za zadanie jest wliczana do wymaganej tylko wtedy, gdy zadanie jest wykonane, tzn. isDone = 1.

IntVar boundReward = new IntVar(store, quest.getQuestName() +"p"+ quest.getQuestPart() +"BoundReward",quest.getReward().min(),quest.getReward().max()); boundReward.addDom(0,0); // 0 albo nagroda rewardsBound.add(boundReward); store.impose(new XmulYeqZ(quest.getIsDone(),quest.getReward(),boundReward)); 3.2.6 Wymagana nagroda Aby wymagania co do sumarycznej były uwzględnione, konieczne jest zastosowanie ograniczenia na sumę nagród wykonanych zadań (isDone = 1)

int[] weights = Ints.toArray(onesArray); store.impose(new Linear(store,rewardsBound.toArray(new IntVar[rewardsBound.size()]),weights,">",requiredExperience-1)); 3.2.7 Odległości między zadaniami Aby uwzględnić odległości zadań, konieczna jest wiedza na temat tego, które zadanie jest wykonywane po którym. W związku z tym, ograniczenia wynikające z odległości są uwzględniane, jeżeli czas rozpoczęcia zadania jest mniejszy od innego, oraz oba są wykonywane (isDone = 1) PrimitiveConstraint c10 = new XltY(previousQuest.getBeginTime(),quest.getBeginTime()); PrimitiveConstraint c9 = new XplusYeqC(quest.getIsDone(),previousQuest.getIsDone(),2); PrimitiveConstraint c1 = new And(c9,c10); (…) store.impose(new IfThen(c1,c8));

5

Dystans dzielący oba wykonywane zadania wyznaczany jest na podstawie różnicy pomiędzy współrzędnymi x oraz y.

IntVar distanceX = new IntVar(store,"DistanceX" + quest.getQuestName() + "from" + previousQuest.getQuestName(),0,maxDimension); PrimitiveConstraint c2 = new Distance(quest.getPositionX(),previousQuest.getPositionX(),distanceX); IntVar distanceY = new IntVar(store,"DistanceY" + quest.getQuestName() + "from" + previousQuest.getQuestName(),0,maxDimension); PrimitiveConstraint c3 = new Distance(quest.getPositionY(),previousQuest.getPositionY(),distanceY); A całkowity minimalny czas jaki powinien dzielić te dwa zadania to suma czasu wykonywania oraz czasu potrzebnego na pokonanie dystansu pomiędzy nimi.

IntVar timeCost = new IntVar(store,previousQuest.getQuestName() + "p" + previousQuest.getQuestPart() + "TimeCost",0,maxTime.max()); IntVar travelCost = new IntVar(store,"travelCost" + quest.getQuestName() + "from" + previousQuest.getQuestName(),0,maxDimension); IntVar totalTimeCost = new IntVar(store,previousQuest.getQuestName() + "p" + previousQuest.getQuestPart() + "TotalTimeCost",0,maxTime.max()); PrimitiveConstraint c4 = new XplusYeqZ(previousQuest.getBeginTime(),previousQuest.getDuration(),timeCost); PrimitiveConstraint c5 = new XplusYeqZ(distanceX,distanceY,travelCost); PrimitiveConstraint c6 = new XplusYeqZ(timeCost,travelCost,totalTimeCost); PrimitiveConstraint c7 = new XlteqY(totalTimeCost,quest.getBeginTime());// PrimitiveConstraint c8 = new And(new PrimitiveConstraint[] {c2,c3,c4,c5,c6,c7}); store.impose(new IfThen(c1,c8));

3.3 APLIKACJA 3.3.1

Zakładka „Search”

6

Aplikacja pozwala na wczytanie standardowego zestawu zadań oraz wpisania wymaganej nagrody. Dodatkowo możliwy jest wybór większości parametrów wymaganych przez bibliotekę Jacop. Przycisk „Reset” usuwa dane, a przycisk „Select and schedule quests” rozwiązuje zadanie. 3.3.2

Zakładka „Quest Settings”

Zakładka „Quest Settings” pozwala na ustalenie parametrów zadania które zostanie dodane po kliknięciu na mapie. Błędy we wprowadzanych danych wyświetlane są w miejscu poniżej parametrów.

7

3.3.3

Rozwiązane zadanie

Aplikacja prezentuje rozwiązanie na mapie, oznaczając kolorem czerwonym niewykonywane zadanie, a niebieskim – wykonywane. Dodatkowo, aplikacja rysuje ścieżkę zgodną z kolejnością wykonywania zadań kolorem zielonym.

8

4 PROBLEM OPTYMALIZACJI 4.1 ZADANIE Analizowany problem optymalizacji polega na znajdowaniu optymalnej trasy przejścia z punktu A do punktu B. Poprzez optymalną trasę rozumie się tutaj drogę o najmniejszym koszcie przebycia (niekoniecznie najkrótszą w sensie geograficznym), przy uwzględnieniu pewnych czynników zewnętrznych. Ukonkretniając, przykładowe zadanie można sformułować następująco: Żaglówka płynie z punktu A do punktu B. Porusza się po dyskretnej mapie, na której w różnych miejscach znajdują się m.in. statyczne przeszkody. Uwzględniając kierunek wiatru, znaleźć optymalną trasę (minimalizacja czasu przejścia).

4.2 REALIZACJA 4.2.1 Wykorzystana struktura Do rozwiązania tego zadania przy użyciu JaCoPa wykorzystano ograniczenie NetworkFlow. Definiuje ono problem sieci rozpływu o minimalnym koszcie. Można go przedstawić za pomocą grafu skierowanego i następujących zmiennych:     

N – zestaw węzłów (node), A – zestaw łuków (arc), l, u – dolna i górna funkcja pojemności łuków, c – funkcja kosztów łuków (cost), b – bilans masowy węzłów (balance).

Pomimo iż teoretycznie struktura wymaga grafu skierowanego (łuki tylko w jednym kierunku), to w praktyce nic nie stoi na przeszkodzie, aby utworzyć łuki w dwóch kierunkach (np. połączenie C->B i B->C). Solver w trakcie obliczeń na pewno uwzględni takie rozwiązanie, ale nigdy go nie wybierze, bo będzie nieoptymalne (cofamy się od punktu docelowego). Adaptując sieć przepływową do postawionego zadania, zmienne grafu można interpretować jako:    



węzły – pola mapy (mapa w kształcie prostokąta), łuki – przejścia między węzłami; zakłada się istnienie połączeń poziomych, pionowych i na skos (węzeł wewnętrzny będzie miał 8 połączeń), pominięto dolną i górną funkcję pojemności łuków, funkcja kosztów określa koszt (można go potraktować jako czas) przepłynięcia z jednego węzła do węzła sąsiedniego; odpowiednio dobierając wartości kosztu, możliwe jest zamodelowanie np. pól mapy stanowiących jakąś przeszkodę (bardzo wysoki koszt, niemożliwy do przekroczenia) oraz szybkość poruszania się żaglówki w zależności od kierunku wiatru (np. niski koszt, jeśli kierunek i zwrot łuku jest zgodny z kierunkiem i zwrotem wiatru, wysoki jeśli łuk skierowany jest przeciwnie do wiatru), bilans masowy – założono, że w sieci istnieją tylko dwa węzły o niezerowym bilansie, reprezentują one punkt startowy (bilans = 1) i docelowy (bilans = -1).

Graficzne przedstawienie:

9

Start – balans = 1

Cel – balans = -1

4.2.2 Realizacja programowa Sieć tworzona jest za pomocą klasy NetworkBuilder, z wykorzystaniem węzłów (Node) i łuków (Arc) zdefiniowanych w JaCoP.constraints.netflow. Na potrzeby zadania utworzono dodatkową klasę MapBuilder, której zadaniem jest wygenerowanie listy obiektów klasy NodeMap, które przechowują wszystkie informacje na temat węzła: id, położenie, balans, lista sąsiadów, z uwzględnieniem kierunków. Pola obiektów NodeMap są „czysto” liczbowe, tzn. nie są powiązane ze zmiennymi używanymi w JaCoPie. Klasa OptimizationProblemModel dostarcza do Solvera wszystkie parametry modelu. Dla lepszego zrozumienia załączono fragment diagramu UML:

10

W pierwszym kroku pracy solvera zainicjalizowane zostają odpowiednie zmienne i tworzona jest lista skonfigurowanych węzłów:

Za pomocą danych przechowywanych w obiekcie „map” możliwe jest dynamiczne (za pomocą dwóch pętli) utworzenie właściwej sieci wykorzystywanej przez JaCoPa:

Ostatni etap to utworzenie zmiennej sumarycznego kosztu przepływu i inicjalizacja procesu poszukiwania rozwiązania:

11

4.2.3 Ogólna struktura programu Moduł programowy został zaprojektowany w sposób następujący:  

Model przechowuje wszystkie informacje na temat problemu (wielkość mapy, punkty charakterystyczne, koszty, rozwiązanie problemu itd.). Controller – zawiera „główną” instancję Modelu i udostępnia referencję do niej do klasy wyświetlającej (Display) i rozwiązującej problem (Solver). Controller obsługuje akcje użytkownika, odczytuje dane z GUI itp.

Taka hierarchia pozwala na zachowanie czytelnego przepływu informacji o problemie, gdyż wszystkie one są dostępne w jednym obiekcie.

4.3 APLIKACJA Domyślny widok:

W poniższych podpunktach zamieszczono szczegółowy opis poszczególnych sekcji programu oraz przykład jego działania. 12

4.3.1 Parametry problemu W lewym panelu aplikacji możliwe jest parametryzowanie problemu, czyli określenie:   

wielkości mapy (max 10x10), kierunku wiatru (poprzez kliknięcie na odpowiedniej „strzałce”), kosztów przepływu względem wiatru (1-100).

Wartości wprowadzane przez użytkownika są na bieżąco sprawdzane i jeśli są błędne, to pole tekstowe podświetla się na czerwono i przycisk Solve jest blokowany. Zmiana wartości w polu tekstowym powinna zostać zatwierdzona przyciskiem Enter.

4.3.2

Mapa

13

Punkty charakterystyczne:     

Zielony znacznik – punkt startowy. Czerwony znacznik – punkt docelowy. Krzyżyk – przeszkoda. Pola oznaczone białą ramką – wyznaczona optymalna trasa. Nad mapą znajduje się wyświetlacz pokazujący sumaryczny koszt trasy.

Aby zmienić na mapie położenie punku startowego i docelowego lub dodać/usunąć przeszkodę, należy kliknąć na odpowiedni przyciski po lewej stronie mapy i kursorem kliknąć w odpowiednie pole. 4.3.3 Działanie programu i przykład Obsługa programu jest bardzo prosta. Jeżeli użytkownik poda poprawne wartości parametrów to wystarczy wcisnąć przycisk Solve i po chwili zostanie wyznaczone rozwiązanie. Jeśli chodzi o czas obliczeń, to dla największej mapy (10x10) wynosi on około 5 sekund, dla mniejszych map to ułamki sekundy. Od strony technicznej warto podkreślić, że funkcja wyznaczająca rozwiązanie uruchamiana jest w innym wątku, odseparowanym od GUI. Oznacza to w trakcie trwania obliczeń można przechodzić między zakładkami aplikacji. Niemniej jednak ze względów bezpieczeństwa, wszystkie kontrolki zakładki Optimaztion na czas obliczeń są blokowane:

Na poniższych zrzutach zaprezentowano przykładowy problem (widać jak zmienia się trasa w zależności od wiatru):

14

15

5 ORGANIZACJA PRACY – GIT Jak już wspomniano we wstępie, kolaboracja nad projektem była wspierana przez system kontroli wersji Git. Repozytorium postanowiono zorganizować zgodnie z ogólnie przyjętym schematem „workflowu”. Zakłada on występowanie kilku odseparowanych „ścieżek rozwoju”, gałęzi (ang. branch):   

master – tutaj pojawiają się tylko stabilne wersje programu, gotowe do udostępnienia, develop – wersje stabilne, ale jeszcze nie przetestowane, feature – gałęzie o skończonym czasie istnienia, rozwijane są w nich odseparowane funkcje programu.

http://www.marvinlabs.com/2013/06/18/our-git-workflow-cheatsheet/

Zdalne repozytorium przechowywane było na serwerze GitHub. Jako iż projekt był stosunkowo niewielki i uczestniczyło w nim tylko dwóch kolaborantów, pojawiły się jedynie dwie gałęzie typu feature i dwie wersje stabilne. Na następnej stronie zamieszczono graf przepływu pracy. Widać na nim wyraźnie dwie równoległe gałęzie master (niebieska) i develop (różowa) oraz odchodzące z niej dwie funkcje (filoletowa i zielona). W pewnym momencie pojawia się też gałąź gh-pages (żółta), której istnienie wyjaśniono w następnym rozdziale. Graf wygląda niemal podręcznikowo. W związku z tym, że było to pierwsze zetknięcie się autorów z taką organizacją Gita, to niestety komentarze do niektórych commitów pozostawiają wiele do życzenia.

16

17

6 DOKUMENTACJA Serwis GitHub umożliwia utworzenie własnej strony internetowej dla projektu (GitHub Pages). Wykonuje się to poprzez utworzenie w repozytorium gałęzi o nazwie „gh-pages”, a następnie umieszczeniu w niej plików źródłowych strony WWW, która później będzie dostępna online pod specjalnym adresem. Otrzymujemy w ten sposób nijako darmowy hosting. Dokumentacja Javadoc została wygenerowana za pomocą skryptu Gradle, umieszczona na gh-pages i jest dostępna online.

Odnośniki:  Repozytorium GitHub: https://github.com/rafakob/PogrFX  Javadoc: http://rafakob.github.io/PogrFX/

18