Documente Academic
Documente Profesional
Documente Cultură
FACULTATEA DE INFORMATICĂ
LUCRARE DE LICENŢĂ
Satisfacere a Constrângerilor
propusă de
Coordonator ştiinţific
Satisfacere a Constrângerilor
Coordonator ştiinţific
Prin prezenta declar că Lucrarea de licenţă cu titlul “Utilizarea Algoritmilor Genetici pentru
rezolvarea Problemelor de Satisfacere a Constrângerilor” este scrisă de mine şi nu a mai fost
prezentată niciodată la o altă facultate sau instituţie de învăţământ superior din ţară sau
străinătate. De asemenea, declar că toate sursele utilizate, inclusiv cele preluate de pe Internet,
sunt indicate în lucrare, cu respectarea regulilor de evitare a plagiatului: toate fragmentele de
text reproduse exact, chiar şi în traducere proprie din altă limbă, sunt scrise între ghilimele şi
deţin referinţa precisă a sursei:
- reformularea în cuvinte proprii a textelor scrise de către alţi autori deţine referinţa
precisă;
- codul sursă, imagini etc. preluate din proiecte open-source sau alte surse sunt utilizate
cu respectarea drepturilor de autor şi deţin referinţe precise;
Iaşi, data
(semnătura în original)
DECLARAŢIE DE CONSIMŢĂMÂNT
Prin prezenta declar că sunt de acord ca Lucrarea de licenţă cu titlul “ Utilizarea Algoritmilor
Genetici pentru rezolvarea Problemelor de Satisfacere a Constrângerilor”, codul sursă al
programelor şi celelalte conţinuturi (grafice, multimedia, date de test etc.) care însoţesc
această lucrare să fie utilizate în cadrul Facultăţii de Informatică.
Iaşi, data
(semnătura în original)
Cuprins
Introducere .................................................................................................................. 3
Generalităţi ................................................................................................................. 4
Problemă ....................................................................................................... 5
Worker .......................................................................................................... 5
Soluţie ............................................................................................................ 5
JavaFX ......................................................................................................... 5
Piesa .............................................................................................................. 5
Concluzii .................................................................................................................... 5
Introducere
O constrângere reprezintă o relație logică între mai multe variabile, indicând restricții asupra
valorilor posibile pe care o variabilă le poate avea, datorită condiționării dintre acestea și alte
variabile.
În capitolul trei este prezentat un exemplu de modelare a problemei Eternity 2. Aceasta este o
problemă de "edge matching" constând în 256 de piese care trebuie aranjate pe o tablă 16 x
16. Fiecare piesa este compusă din 4 laturi, fiecare având câte o culoare. Rezolvarea constă în
rotirea pieselor și așezarea acestora pe tablă astfel încât oricare două piese adiacente să aibă
aceeași culoare în locul de intersecție. Piesele de la extremitățile tablei (de pe bordură) au
aceeași culoare c0.
Scorul maxim este dat de numărul conexiunilor interioare : 2𝑛(𝑛 − 1) , pentru orice tablă
𝑛 𝑥 𝑛.(pentru Eternity 2 scorul maxim este 480).
Dimensiunea spatiul solutiilor ale problemei Eternity 2 este de 256! 4256 combinații.
Problema Eternity 2 este încadrată în clasa problemelor NP-Complete. Aceasta a fost creată
de către Cristopher Monckton și apoi distribuită de distribuitorul de jucării Tomy UK Ltd. în
iunie 2007. Compania a promis să ofere un premiu de 2 milioane de dolari primei persoane
care va rezolva puzzelul. Până la data scadentă, în decembrie 2010 nu a fost nimeni care să
rezolve complet puzzelul. După această dată nu s-a mai cerut scorul final ci doar o soluție care
să găsească scorul parțial cel mai bun.
In capitolul patru este prezentat un algoritm genetic care încearcă să rezolve problema
modelată în capitolul precedent. În acest capitol sunt prezentate diferite scheme de selecție,
scheme de mutație , de încrucișare, de generare a bordurii și de reparare a soluțiilor stricate.
În capitolul cinci este abordat modul în care este implementată interfața aplicației utilizând
JavaFx 8.0. Aici este prezentat modul în care au fost generate piesele, cum a fost
implementată metoda "drag and drop" pentru mutarea pieselor, modul de încărcare a pieselor
din fișier, cum se poate porni și utiliza solverul, si diferite meniuri disponibile unui utilizator.
Pe lângă modulul de rezolvare a puzzle-lui cu ajutorul algoritmilor genetici, interfața este
realizată ca un joc de gândire pe diferite stagii (fiecare stagiu conține doisprezece niveluri de
diferite dimensiuni), în care utilizatorul este rugat sa rezolve anumite instanțe Eternity 2.
𝑉 = {𝑅1 , 𝑅2 , … , 𝑅𝑛 }
𝐷𝑖 = {1, … , 𝑛}
𝑉 = {𝑆, 𝐸, 𝑁, 𝐷, 𝑀, 𝑂, 𝑅, 𝑌}
𝐷𝑖 = {0, … ,9}
𝐶 = {1000 ∗ 𝑆 + 100 ∗ 𝐸 + 10 ∗ 𝑁 + 𝐷 + 1000 ∗ 𝑀 + 100 ∗ 𝑂 + 10 ∗ 𝑅 + 𝐸
= 10000 ∗ 𝑀 + 1000 ∗ 𝑂 + 100 ∗ 𝑁 + 10 ∗ 𝐸 + 𝑌}
Algoritmii genetici fac parte din categoria algoritmilor de calcul evoluţionist şi sunt
inspiraţi de teoria lui Darwin asupra evoluţiei. Idea calculului evoluţionist a fost introdusă în
1960 de I. Rechenberg în lucrarea intitulată “Evolution strategies”.
Algoritmii genetici au fost aplicaţi cu succes într-o varietate de aplicaţii care necesită
optimizarea globală a soluţiei. Algoritmii genetici se referă la un model introdus şi analizat de
J. Holland în 1975 şi sunt "proceduri adaptive care găsesc soluţia problemei pe baza unui
mecanism de selecţie naturală şi evoluţie genetică" [22]. Algoritmul este des folosit pentru
probleme în care găsirea soluţiei optime nu este uşoară sau cel puţin ineficientă datorită
caracteristicilor căutării probabilistice. Algoritmii genetici codifică o soluţie posibilă la o
problemă specifică într-o singură structură de date numită „cromozom” şi aplică operatori
genetici la aceste structuri astfel încât să menţină informaţiile critice.
Soluția returnată de algoritmul genetic reprezintă cel mai bine adaptat cromozom din
ultima generație.
1.2.1. Modelul general al unui algoritm genetic
INITIALIZARE i = 0
generate P(i)
evaluate P(i)
ITERATIE while (termination conditions not met) {
i = i + 1
select P(i) from P(i-1)
recombine P(i)
mutate P(i)
evaluate P(i)
}
Tabela 1: Schema generală a unui Algoritm Genetic
Funcția de evaluare, numită și funcția fitness este utilizată pentru a măsura calitatea
cromozomilor. Aceasta este de obicei funcţia care reprezintă descrierea problemei.
Când trebuie să rezolvăm o problemă, de obicei ne uităm după anumite soluţii care sunt mai
bune decât alte soluţii obţinute anterior. Spaţiul tuturor soluţiilor este numit spaţiul de căutare
sau spaţiul stărilor. Problemele abordate folosind algoritmi genetici sunt de obicei probleme
pentru care căutarea în spaţiul soluţiilor este o problemă complicată sau chiar (Np-completă).
Soluţiile obţinute folosind algoritmi genetici sunt de obicei considerate ca soluţii bune
deoarece nu este întotdeauna posibil să cunoaştem care este optimul real.
Un alt pas important în algoritmul genetic este cum selectăm părinţii din populaţia curentă
care vor alcătui noua populaţie. Aceasta poate fi făcută în mai multe feluri, dar idea de bază
este de a selecta cei mai buni părinţi, în speranţa că aceştia vor produce cei mai buni copii. În
combinație cu variații ale operatorilor de încrucișare și mutație, selecția trebuie să păstreze un
echilibru între explorare și exploatare.
1.2.4. Operatori de mutație
Operatorul de mutație alege doar un singur candidat şi aleator modifică unele valori
ale acestuia. Mutaţia funcţionează prin alegerea aleatoare a numărului de valori care vor fi
schimbate şi a modului de modificare a acestora.
Solverul OmniCS a fost realizat de către domnul Lector, Dr. Cristian Frăsinaru în cadrul tezei
"Probleme algoritmice în studiul proprietăților ereditare pe grafuri", iar motivația realizării
acestui solver este aceea de "a creat un produs software capabil să rezolve instanțe CSP de
orice tip, bazat pe tehnologia Java, construit pe șabloane de proiectare a codului ușor de
înțeles, OpenSource(având un API public documentat atât pentru modelare cât și pentru
dezvoltare), simplu (la nivel structural cât și în utilizare) și eficient (performanța fiind
comparabilă cu alte solvere)" [8].
Solverul Omnics oferă posibilitatea atașării de "workeri" în cadrul acestuia. Aceștia sunt
adăugați și porniți ca un fir de execuție în cadrul solverului, totodată aceștia mențin
comunicarea.
Pentru a putea porni solverul este necesar a se defini problema CSP utilizată (problema
reginelor, problema comisului voiajor ...), alegerea unui worker specific pentru rezolvare, sau
workerul implicit, iar apoi cu ajutorul metodei "findSolution" din cadrul clasei
"Omnics.Solver" rularea acestuia. Solverul va încerca să ofere o asignare corectă de valori
specifice problemei alese. În Tabela 2 de mai jos este prezentat unul din modurile în care
solverul poate fi pornit, utilizând un worker atașat.
//QueenProblem
int n = 20;
Problem prb = new QueenProblem(n);
Solver solver = new Solver(prb,false);
Worker worker = (new CreateWorker()).create(prb,"QueenProblem",null,
solver,60,500,0.2);
solver.addWorker(worker);
solver.findSolution();
solver.printStatistics();
Tabela 2: Modul de utilizare a solverului OmniCS cu atașarea unui "worker"
Dacă nu se dorește utilizarea unui nou worker, ci doar utilizarea algoritmilor definiți implicit
în pachetul "OmniCS" apelul va fi făcut fără utilizarea metodei addWorker(). In Tabela 3 de
mai jos este prezentat modul în care este realizat acest apel.
//QueenProblem
int n = 20;
Problem prb = new QueenProblem(n);
Solver solver = new Solver(prb);
solver.findSolution();
solver.printStatistics();
Problema pe care solverul o poate rezolva trebuie modelată CSP, R = (V,D,C), având o
mulțime de variabile, domenii pentru acele variabile și un set de constrângeri ce vor trebui
satisfăcute de algoritm. Tabela 4 de mai jos pune in vedere modul în care se poate crea o nouă
problemă. Clasa care va fi extinsă se află în pachetul "omnics.model.Problem".
}
public toString(){
}
}
Tabela 4: Exemplu de derivare a clasei Problem
Acestea sunt doar două din metodele aflate în clasa Problem care vor fi folosite în continuare
în modelarea unei probleme.
În cadrul constructorului se pot defini și seta variabilele care vor fi folosite în cadrul
problemei. Acestea se pot defini urmărind următorul tipar Var <nume_variabilă> =
variable(<numele_variabilei>,<valoare_minimă_domeniu><valoare_maximă_domeni
u>). Pentru domeniu variabilelor se poate utiliza tipul întreg, sau un nou tip Domain in care se
pot specifica valorile din domeniu, dacă nu se dorește lucrul cu intervale continue.
Odată definite variabilele și domeniile acestora se vor specifica constrângeri care peste
această mulțime de variabile pentru care se dorește ca algoritmul să găsească o asignare
corespunzătoare. Aceste constrângeri se pot specifica utilizând modele de constrângeri deja
implementate, sau se pot defini noi constrângeri care să fie utilizate. Exemple: alldiff, gcc,
atleast, atmost, etc.
Pentru a putea defini noi constrângeri trebuie extinsă clasa Constraint și suprascrise metodele
din constructor și funcția de evaluare după exemplul din Tabela 6.
Funcția de evaluare ne permite validarea unor asignări ale variabilei din starea arg0 utilizând
anumite reguli implementate în aceasta funcție. Aceasta va întoarce ALLOWED dacă
evaluarea s-a realizat cu succes sau FORBIDDEN altfel. Tabela 7 de mai jos prezintă modul
în care se pot crea noi constrângeri.
}
@Override
public int eval(State arg0) {
int pieceIdentifierA = arg0.solution().get(this.vara);
int pieceIdentifierB = arg0.solution().get(this.varb);
...
if(this.expr.compareTo("left-right") == 0){
return rVarA == lVarB ? ALLOWED : FORBIDDEN;
} else if(this.expr.compareTo("up-down") == 0){
return dVarA == uVarB ? ALLOWED : FORBIDDEN;
}
return FORBIDDEN;
}
public String toString(){
return "CONS-1x1";
}
}
Tabela 7: Exemplu de constrângere creată
În Tabela 8 este prezentat modul în care este creată problema Eternity 2, iar Diagrama 1
reprezintă diagrama claselor problemei.
}
}
Pentru celelalte tipuri de probleme, cele deja modelate in cadrul OmniCS, au fost utilizate
cele prezente în pachetul Omnics.Sample.
2.3. Worker
Tabela 9 prezintă modul în care este definită o clasă care extinde clasa Worker, și metodele
care trebuiesc implementate.
În cadrul acestei clase se pot defini diferite funcții și diferiți algoritmi de rezolvare a unei
probleme CSP.
public class AlgorithmWorker extends Worker {
public AlgorithmWorker(Solver solver, Problem problem, Solution
initialSol,int problemSize) {
super(solver, problem);
this.startSol = initialSol;
this.problemSize = problemSize;
}
protected void solve() {
...
//System.out.println(this.best.toString());
State st = new State(this.best);
for(Constraint key:this.problem.constraints())
if(key.toString().contains("alldiff"))
System.out.println("allDiff "+key.eval(st));
Solution sol = new Solution(problem);
}
public String statistics(){
...
}
}
Tabela 10: Exemplu de clasa care deriveaza clasa Worker
Pentru a se putea porni solverul este necesar să aibă atașată problema CSP dorită și "worker-
ul" care va fi responsabil de rezolvarea problemei. Problema definită se atașează cu ajutorul
constructorului solver-ului iar workerul cu ajutorul metodei addWorker(), pentru altul decât
cel implicit. Odată atașate acestea două, cu ajutorul metodei findSolution() se solverul va
porni și va încerca găsirii unei assignari care să satisfacă constrângerile.
2.4. Soluție
O soluție este reprezentată de o mulțime de variabile 𝑉 = {𝑣1 , … , 𝑣𝑛 }, fiecare dintre ele având
specificate o valoare din domeniul 𝐷𝑖 . În cadrul solverului OmniCS o soluție pentru problemă
este definita printr-o clasă Solution aflată în pachetul "omnics.solver.solution" prin care se pot
crea soluții pentru o problemă, seta sau actualiza valorile pentru variabile. Setarea unei soluții
pentru o problemă se realizează în cadrul constructorului, setarea, actualizarea, cât și
verificarea existenței a variabilelor în cadrul metodelor existente în clasa Solution. În
porțiunea următoare de cod este prezentată o metodă de lucru cu această clasă.
O constrângere poate fi verificată peste o soluție astfel. Se definește o stare State new
State(solution)pentru a putea evalua o constrângere. O constrângere definită are
suprascrisă metoda eval(<state>) iar în cadrul acestei metode se evaluează soluția.
Pentru a putea evalua toate soluțiile pentru o problemă se utilizează metoda
getConstraints() din clasa Problem. In Tabela 13 este prezentat un mod de a evalua
constrângerile pentru o anumita soluție.
Algoritmul utilizat este definit pentru a rezolva un număr larg de probleme CSP.
Aceste probleme uneori necesită o modelare specifică, în sensul că unele variabile definite nu
trebuie modificate, acestea făcând parte din definirea problemei, având o valoare deja
asignată. De exemplu pentru problema Eternity 2 avem variabilele Sus, Jos, Stânga, Dreapta,
Identificatori și Orientarea, unde variabilele Sus, Jos, Stânga, Dreapta reprezintă culorile
piesei i, Identificatorul reprezintă piesa care se află pe poziția i pe tablă și Orientarea o
reprezintă numărul de rotire ale piesei care se află pe poziția i. O asignare reprezintă găsirea
unei secvențe de identificatori i având orientarea o astfel încât să fie satisfăcute toate
constrângerile de "edge-matching". Dacă am încerca să modificam și variabilele Sus, Jos,
Stânga, Dreapta am modifica culorile pieselor încărcate inițial, ceea ce este total greșit. Pentru
alte probleme,este necesară găsirea unei assignari a tuturor variabilelor pentru a satisface toate
constrângerile. De aici se poate observa că pentru anumite probleme este necesară o strategie
diferită de rezolvare, iar pentru aceasta au fost definite mai multe strategii de lucru, toate
acestea utilizând următoarea interfață.
Pentru partea practică au fost utilizați doi algoritmi genetici diferiți implementați prin
două clase care extind clasa Worker. Primul, denumit DefaultWorker care încearcă să ofere o
asignare optimă pentru probleme CSP mai generale, și unul denumit EternityWorker care
încearcă să ofere o asignare optimă pentru o problemă specifică, problema Eternity 2.
Diferențele sunt date de metoda "solve" care este implementată diferit.
}
public String statistics(){
...
}
}
public class EternityWorker extends Worker{
public EternityWorker(Solver solver, Problem problem,long workingtime,int
popsize,double alphaSelectProbability) {
super(solver, problem);
...
}
@Override
protected void solve() {
...
}
public String statistics(){
...
}
}
Tabela 16: Definirea celor două clase utilizate în cadrul metodei "create" aflată în interfața
AbstractCreateWorker
Diagrama 2: Modul de definire al claselor privind alegerea unui "worker"
Prin primul algoritm, definit în clasa DefaultWorker s-a încercat rezolvarea prin
algoritmi genetici a problemelor CSP definite în pachetul "Omnics.Sample" (problema
rucsacului, problema reginelor, problema pătratului magic și altele).
Pentru inițializarea populației inițiale au fost selectate valori aleatoare din domeniul
fiecărei variabile.
Ca și metoda de selecție este utilizată selecția bazată pe schema Gauss: aceasta asigură
posibilitatea luării în considerare și a cromozomilor care nu au obținut valori mari pentru
funcția de evaluare asigurând astfel diversitatea procesului (oferă șanse mai mari de evoluție
și cromozomilor mai slabi).
Operatorul de mutație alege un singur candidat şi aleator modifică unele valori ale
variabilelor acestuia. Mutaţia funcţionează prin alegerea aleatoare a numărului de valori care
vor fi schimbate şi a modului de modificare a acestora. In Tabela 17 este prezentat un
exemplu de modificare a 2 cromozomi (pentru problema reginelor, unde fiecare valoare
reprezintă poziția pe linie unde se află aceasta) primul având trei puncte de modificare iar al
doilea doar două puncte de modificare. În funcţie de dimensiunea cromozomului se alege
numărul de puncte pentru care se aplică mutaţia.
Tabela 17: Exemplu de aplicare a operatorului de mutație
Un pas important într-un algoritm genetic este alegerea corectă a funcției fitness, o
funcție fitness aleasă incorect duce de cele mai multe ori la rezultate eronate.
De exemplu pentru problema pătratului magic, unde există un număr mare de
constrângeri , va fi utilizată o funcție fitness care evaluează număr constrângerilor 1 -
satisfăcute / numărul total de constrângeri. De altfel pentru problema Rucsacului, unde există
o singură constrângere de satisfăcut, a1x1+a2x2+...+anxn = n, funcția va trebui să furnizeze o
valoare în funcție de valorile celor n variabile din constrângere în intervalul [0,1], altfel, dacă
nu s-ar ține cont de acest fapt rezultatul furnizat va fi 0 dacă constrângerea nu este satisfăcută
sau 1 dacă este satisfăcută, lucru care ar afecta puterea de rezolvare a algoritmului.
@Override
public double eval(Solution sol) {
...
for(Constraint ct : prb.constraints()){
String[] exp = ct.toString().split("=");
maxNr = Integer.parseInt(exp[1]);
String[] expa = exp[0].split("\\+");
for(String st : expa){
String[] nmVl = st.split("\\*");
...
}
}
}
}
Tabela 20: Funcție de evaluare(fitness) în cazul problemei Rucsacului.
Pentru a putea satisface faptul ca functiile de evaluare difera in funcţie de problemă a fost
definită, utilizând pattern-ul factory o clasă "CreateFinessFunction" care returneaza obiecte de
tipul FitnessFunction specifice problemei alese.
Schema algoritmului este cea prezentată în primul capitol, iar algoritmul se oprește în
momentul în care a fost găsită o soluție optimă (având valoare funcției de evaluare egală cu
zero), sau în momentul când timpul setat la inițializarea workerului a fost depășit.
3. Modelarea CSP a problemei Eternity 2
3.3.Puzzle-lul Eternity 2:
Problema Eternity 2 este o problemă de "edge matching" constând în 256 de piese care trebuie
aranjate pe o tabla 16 x 16. Fiecare piesa este compusă din 4 laturi, fiecare având câte o
culoare. Rezolvarea constă în rotirea pieselor și așezarea acestora pe tablă astfel încât oricare
două piese adiacente să aibă aceeași culoare la locul de intersecție.
Scorul maxim este dat de numărul conexiunilor interioare : 2𝑛(𝑛 − 1) , pentru orice tablă
𝑛 𝑥 𝑛.(pentru Eternity 2 scorul maxim este 480).
Problema Eternity 2 a fost inițial creată de către Cristopher Monckton și apoi distribuită de
distribuitorul de jucării Tomy UK Ltd. în iunie 2007. Compania a promis să ofere un premiu
de 2 milioane de dolari primei persoane care va rezolva puzzle-lul. Pană la data scadentă, în
decembrie 2010 nu a fost nimeni care să rezolve complet puzzle-lul. După această dată nu s-a
mai cerut scorul final ci doar o soluție care să găsească scorul parțial cel mai bun.
Nume Metoda N
Ansotegui et al. (2008) SAT/CSP 7
Schaus and Deville (2008) CSP/Tabu 16
Heule (2008) SAT 14
Munoz et al. (2009) GA, MOEA 16
Wang and Chiang (2010) Tabu search 16
Coelho et al. (2010) GVNS 16
Vancroonenburg et al. (2010) hyper-heuristic 16
Tabela 22: Rezultatele privind rezolvarea problemei Eternity 2 obținute în timp
Figura 3: Exemple de potriviri (primele două imagini) și nepotriviri (ultimele două imagini) între piese
[21]
4. Rezolvarea problemei Eternity 2 cu Algoritmi Genetici
inițializarea populației
evaluarea candidaților
while ( nu este satisfăcută condiția de terminare){
selectează indivizii
aplică încrucișează
aplică mutația
evaluează
}
Tabela 23: Schema generală a unui algoritm genetic
Pentru a rezolva problema cu algoritmi genetici inițial aceasta trebuie modelată ca o problemă
CSP având variabilele, domeniile și constrângerile definite mai sus.
Mulțimea 1, 5, 4, 3, 1 reprezintă piesa din Figura 6, iar după cum se poate observa piesa este
rotită.
Intr-o primă etapă se va încerca găsirea modurilor de așezare a bordurii, adică identificatorii I
si orientarea O pentru care constrângerea ca bordura sa fie 0 sa fie satisfăcută și funcția de
cost care leagă piesele din contur să fie maximă. De exemplu pentru o tablă n x n costul
maxim va fi 𝑛(𝑛 − 1), pentru exemplul din figura următoare costul va fi egal cu 4. Vom
încerca folosind tehnica backtracking să generăm aceste borduri și să le menținem în memorie
pe parcursul evoluției algoritmului pentru a le putea utiliza ulterior în momentul utilizării
euristicilor de reparare a unor soluții și în pasul de inițializare a algoritmului genetic.
Intr-un prim pas se generează o populație de PopSize cromozomi fiecare conținând copii ale
soluției inițiale. Apoi pentru fiecare cromozom din populație se aleg aleator identificatorii Ii j
și o bordură din cele generate la pasul anterior, tot aleator.
lista_soluții = null
inițializarea(soluție_inițială,borduri,popSize){
for(i = 0 .. popSize){
soluție = new Solution(soluție_inițială)
alege aleator Identificatorii I intre 0 ... n fără ai utiliza
pe cei din bordură și fără duplicate
alege o bordură din cele m găsite
formează un cromozom și adaugă-l la listă
}
}
Tabela 25: Pasul de inițializare cu selectarea bordurii al algoritmului genetic
4.5.Operatorul de selecție
Ca și metode de selecție sunt propuse două metode, fiecare cu avantajele și dezavantajele ei.
4.5.1. Schema de selecţie utilizând Roata Norocului.
În această metodă fiecare individ din populaţia curentă este reprezentat printr-un spaţiu
proporţional cu valoarea funcţiei lui de evaluare. Prin eșantionări aleatoare succesive din acest
spaţiu de reprezentare a cromozomilor asigură că cei mai buni cromozomi au șanse mai mari
sa fie selectați la un anumit pas decât cei mai slabi. Această metodă de selecție va avea
probleme în momentul în care valoarea funcției de evaluare diferă foarte mult de la un
cromozom la altul. Numărul estimat de copii pe care le primește un individ este proporțional
cu fitnessul sau împărțit la fitnessul total al populației. Dacă un candidat i are fitnessul fi
𝑓(𝑖)
atunci probabilitatea ca acesta să fie selectat va fi ∑𝑛
unde n reprezintă dimensiunea
0 𝑓(𝑖)
populației. De exemplu dacă cel mai bun cromozom are valoarea funcției de evaluare mare
(care va ocupa 90% din spaţiul de reprezentare) iar restul cromozomilor au valori ale
funcțiilor de evaluare foarte mici această metoda va selecta de foarte multe ori cromozomul
cel mai bun ducând în final la degenerarea populaţiei.
for i = 0 ... POP_SIZE
eval[i] <- fitness(P[i]);
total <- 0
for i = 0 ... POP_SIZE
total <- total + fitness[i]
for i = 0 ... POP_SIZE
p[i] = eval[i] /total;
q[0] <- 0
for i = 0 ... POP_SIZE
q[i+1] <- q[i] + p[i]
for i = 0 ... POP_SIZE{
r <- random(0,1)
selectează individul j, unde q[j] < r <= q[j+1]
}
Tabela 26: Algoritmul utilizat pentru schema de selecție Roata Norocului
De aceea această metoda de selecție nu este una din cele mai bune.
Avantajul acestei metode reprezintă faptul că presiunea de selecție poate fi ajustată ușor
modificând α.
4.5.3. Elitism
Într-un algoritm genetic standard nu se păstrează soluția cea mai bună obținută pană la un
moment dat, mărind șansele de a pierde obținerea celor mai bune soluții posibil. Strategiile
ne-elitiste sunt cele care vin să depășească această problemă prin copierea celor mai buni n
membri din generația curentă în generația următoare. Deși această strategie poate mări viteza
de dominare a unei populații de către un șir de indivizi puternic (șir cu o valoare fitness mare
a fitness-ului), ea mărește performanțele unui algoritm genetic folosind înlocuirea
generațiilor. În cazul problemei Eternity 2 este păstrat doar primul individ, adică cazul în care
n = 1, această metodă numindu-se "Golden Cage Model".
4.6. Operatorul de Mutație
Operatorul de mutație obișnuit a fost împărțit în mai multe scheme de mutație care vor fi
prezentate în cele ce urmează.
Această schemă de mutație descrisă de Monuz [2], presupune rotirea unei regiuni 2x2.Prin
aceasta se încercă găsirea a mai multor combinații fără a strica potrivirile din interiorul
regiunii.
Figura 12:Mutație prin rotirea regiunilor
Operatorul de mutație prin interschimbarea a m piese alăturate a fost împărțit în doi operatori.
Primul generează și așează cele m piese pe verticală, iar cel de-al doilea pe orizontală. Figura
15 de mai jos pune în evidență modul în care sunt așezate cele m piese pe orizontală.
Figura 15: Exemplu de astfel de mutație a 3 piese
Încrucișarea uniformă utilizează același principiu ca cea de mai sus, doar că nu utilizează
regiuni de dimensiune 2x2. Se utilizează același procedeu de eliminarea a duplicatelor.
Pentru interschimbarea unei regiuni mxm corecte, se alege o regiune din părintele A în
care între cele mxm piese constrângerile sunt satisfăcute. Apoi aceasta regiune este adăugată
la o locație aleatoare în părintele B, astfel generându-se cei doi copii. Prin această metodă se
încearcă crearea unor soluții mai bune decât cei doi părinți selectați. Această metodă este
prezentată Tabela 18 de mai jos.
selectează părintele A și părintele B aleator
găsește în părintele A o regiune R mxm corectă
alege o locație L aleatoare din părintele B
copie cei doi cromozomi
scoate din părintele A toate piesele din regiunea R
scoate din părintele B toate piesele din regiunea R
adaugă piesele din cei doi părinți din locații în lista LA si LB
interschimbă cele 2 regiuni
umple locurile ramase libere cu piese din lista LA si lista LB
Tabela 18: Algoritmul utilizat pentru operatorul de încrucișare a unei regiuni mxm.
În această metodă se aleg m piese alăturate pentru care constrângerile sunt satisfăcute,
iar acestea vor fi adăugate ulterior interschimbate în cei doi cromozomi creați.
Figura 20:Exemplu de încrucișare prin interschimbarea unei regiuni 1x3
Această metodă este asemănătoare cu metoda Interschimbării regiunilor mxm corecte doar că,
atât regiunea din părintele A, cât și regiunea din părintele B trebuie să aibă constrângerile din
interiorul acestora satisfăcute.
Pentru repararea soluțiilor sunt propuse trei metode, pentru trei simptome diferite. A
fost concepută o funcție prin care se elimina blocajele, alta pentru a rearanja regiunile 2x2 și a
treia funcție pentru a rearanja bordura.
Prin această metodă se încearcă reașezarea regiunilor 2x2 astfel încât cât mai multe
constrângeri dintre regiuni să fie satisfăcute.
Se verifică un număr n de indivizi selectați aleator, iar dacă aceștia îndeplinesc criteriile de
reparare, algoritmul corespunzător este apelat. Algoritmul pentru repararea bordurii este ales
în momentul în care valoare funcției fitness pentru bordura are o valoare ridicată. Pentru
celelalte simptome sunt folosite valorile funcțiilor fitness corespunzătoare.
5. Interfața aplicației
5.1. JavaFx
JavaFX este cea mai nouă implementare a platformei de dezvoltare pentru interfețe
GUI de client. Cu toate că este încă într-un stadiu incipient, are avantaje față de vechiul
Swing. Versiunea 2.2, care a fost publicată pe 14 august 2012 este un pas înainte către o nouă
generație de aplicații multimedia pe Internet [20].
5.2. Piesa
Pentru a putea asigna unei piese cele patru culori se folosește clasa "PieceColor" care va fi
setată în constructorul clasei "Piece", aceasta putând fi modificată doar la inițializarea tablei.
Cele patru culori , în intervalul [0..20] vor fi date ca parametru, iar in funcție de acestea vor fi
generate 12 culori și o funcție de colorare. Fiecare latură a piesei va fi colorată pe exterior cu
o culoare, iar în interior se va aplica o funcție reprezentând un poligon. Poligonul astfel
desenat va fi umplut cu o culoare diferita de cea a laturii. În exemplul din Tabela 32 este
arătat modul în care sunt alese culorile pentru o latură a unei piese.
case 0:
form = 0;
c = Color.BLUE;
ci = Color.DARKGRAY;
case 1:
form = 1;
c = Color.CORNSILK;
ci = Color.CYAN;
...
Tabela 32: Algoritmul selectare a culorilor pieselor
După ce au fost inițializate culorile și alese funcțiile de colorare pentru fiecare latură se va
utiliza "GraphicsContext-ul canvas-ului" împreună cu următorul algoritm de desenare.
gc.setFill(this.cUp);
gc.fillPolygon(new double[]{0, xCentru, xMax},
new double[]{0, yCentru, 0}, 3);
Tabela 33: Algoritmul de colorare a laturilor pieselor
Următoarea etapă în algoritmul de desenare este de a reprezenta cele doua diagonale, pentru
aceasta a fost utilizată dimensiunea liniei de 1px iar culoare "Color.Black".
gc.setStroke(Color.BLACK);
gc.setLineWidth(1);
gc.strokeLine(0, 0, xMax, yMax);
gc.strokeLine(xMax, 0, 0, yMax);
Tabela 34: Algoritmul de desenare a diagonalelor pieselor
A treia etapă este de a alege punctele poligonului , respectiv xi și yi, care se potrivesc pe latura
actuală a piesei. De exemplu pentru latura stângă se vor alege acei {𝑥𝑖 , 𝑦𝑖 } din xcentru
𝑥𝑐𝑒𝑛𝑡𝑟𝑢 pana la 𝑥𝑚𝑎𝑥 .
xi = FunctionDrawSingleton.getInstance().getX1();
yi = FunctionDrawSingleton.getInstance().getY1();
xiinside = FunctionDrawSingleton.getInstance().getX1inside();
yiinside = FunctionDrawSingleton.getInstance().getY1inside();
for(int i=0;i<xi.length;i++){
if(xi[i] > xC){...}
if(yiinside[i] > yC){...}
}
gc.setFill(cForm);
gc.fillPolygon(newY,newX,index);
gc.strokePolygon(newY,newX,index);
gc.setFill(cIForm);
gc.fillPolygon(newYinside,newXinside,index1);
Tabela 35:Algoritmul de desenare a poligonului
Poligoanele folosite în etapa de mai sus vor fi generate o singură dată la inițializarea tablei, și
utilizate ulterior. Pentru a putea reține aceste poligoane a fost folosită o clasă "PolygonDraw"
utilizând patternul Singleton.
Au fost definite patru poligoane, fiecare dintre ele fiind prezentate în cele ce urmează:
Graficul 1
Graficul 2
Ultimul poligon este creat folosind următoarele puncte {𝑥𝐶𝑒 − 10, 𝑦𝐶𝑒 + 10}, {𝑥𝐶𝑒 +
10, 𝑦𝐶𝑒 + 30}, {𝑥𝐶𝑒 + 10, 𝑦𝐶𝑒 + 30}, {𝑥𝐶𝑒 + 10, 𝑦𝐶𝑒 + 10}, {𝑥𝐶𝑒 + 30, 𝑦𝐶𝑒 + 10}, {𝑥𝐶𝑒 +
30, 𝑦𝐶𝑒 − 10}, {𝑥𝐶𝑒 + 10, 𝑦𝐶𝑒 − 10}, {𝑥𝐶𝑒 + 10, 𝑦𝐶𝑒 − 30}, {𝑥𝐶𝑒 − 10, 𝑦𝐶𝑒 − 30}, {𝑥𝐶𝑒 −
10, 𝑦𝐶𝑒 − 10}, {𝑥𝐶𝑒 − 30, 𝑦𝐶𝑒 − 10}, {𝑥𝐶𝑒 − 30, 𝑦𝐶𝑒 + 10} reprezentând forma unei cruci.
Poligonul din interiorul acesteia este reprezentat de un cerc având următoarele coordonate
𝑥𝑖 = (1 + 𝑡) ∗ sin 𝑡 respectiv 𝑦𝑖 = (1 + 𝑡) ∗ cos 𝑡
Graficul 4
Toate cele 4 poligoane cât și cele din interior, atunci când sunt create sunt centrate
după dimensiunile unei piese standard de dimensiune 200 x 200, după care în momentul când
acestea sunt utilizate se vor scala pe intervalul corespunzător dimensiunii piesei respective.
Pentru aceasta s-a utilizat următorul algoritm de scalare a unei valori dintr-un interval în alt
interval.
În orice moment în care o piesă se află pe tablă se știe numărul de rotații care au fost
parcurse, pentru a realiza o nouă rotație se aduce piesa în poziția inițială apoi se aplică
numărul de rotații dorite înmulțite cu 90 grade. După această etapă este actualizat numărul de
rotații actuale. Intervalul maxim de rotații pentru o piesa este [0,3] , corespunzator
{0°, 90°, 180°, 270°}.
Modalitatea de rotire descrisă mai sus nu este folosită atunci când utilizatorul dorește
rotirea unei piese, acesta având acces doar la următoarele metode de rotire:
Rotire piesa dreapta cu tranziție : Într-o prima etapă se efectuează scalarea piesei, apoi rotirea
acesteia cu (𝑛𝑢𝑚𝑎𝑟𝑢𝑙_𝑑𝑒_𝑟𝑜𝑡𝑎𝑡𝑖𝑖 + 1) ∗ 90° în același timp, iar în faza a doua se aduce
piesa la dimensiunile inițiale. Cât timp au loc aceste tranziții utilizatorul nu poate realiza o
noua rotire la piesa curentă. Următorul algoritm, din Tabela 38 de mai jos, pune în evidență
modul și timpii folosiți pentru tranziții:
rt.setByAngle(90);
st.play();
rt.play();
ScaleTransition st1 = new ScaleTransition(Duration.millis(500),
this.canvas);
st1.setByX(-0.2f);
st1.setByY(-0.2f);
rt.onFinishedProperty().set(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent actionEvent) {
st1.play();
}
});
Tabela 38: Algoritmul privind rotirea unei piese cu animație
Rotire piesă simplă cu tranziție: Analog ca mai sus, doar cu precizarea că rotirea se face
folosind (𝑛𝑢𝑚𝑎𝑟𝑢𝑙_𝑑𝑒_𝑟𝑜𝑡𝑎𝑡𝑖𝑖 + 1) ∗ −90°.
Locul în care vor fi puse inițial piesele, cât și cel în care vor fi acestea așezate sunt
reprezentate de un "Gridpane" de dimensiune n x n. Pe fiecare poziție a acestui grid se vor
găsi obiecte de tip "HBox" de dimensiunile unei piese, iar in interiorul acestora "Canvasul"
piesei din acea locație. Legătura dintre piesă și canvas-ul ei este reprezentată de id acestora
care va fi identic. Pentru a putea fi manevrate aceste obiecte au fost utilizate următoarele
evenimente pentru obiectele din tabelul inițial: setOnMouseClicked, setOnMouseEntered,
setOnMouseDragged, setOnMouseReleased. În tabelul destinație vor fi iniţial puse obiecte, de
tip "HBox" având dimensiunea pieselor, pentru a marca locul în care obiectele din tabelul
inițial pot fi lăsate.
Cele două tabele sunt reținute într-o clasă Table de tip Singleton, in care sunt definite toate
acţiunile pe care un utilizator le poate face din momentul in care piesele au fost încărcate până
când acestea sunt așezate corect pe tabla target.
În continuare sunt descrise acțiunile pe care utilizatorul le poate face cu obiectele din acest
grid.
La inițializarea celor două table se vor folosi două liste una de tip "HBox" pentru piesele din
tabelul inițial și alta de același tip pentru piesele din tabelul destinație. Acest lucru conferă un
avantaj, deoarece obiectele "HBox" inițiale, pentru care au fost definite handlere de
evenimente trebuie reținute, chiar dacă au fost scoase din cele două tabele, pentru o utilizare
ulterioară, eg: resetare tabele, pornire algoritm de rezolvare prin algoritmi genetici. În
momentul în care utilizatorul apasă pe un obiect de pe tabla sursă se rețin în două variabile
pozițiile x,y din scenă pentru a putea ulterior transla o piesă.
orSceneX = event.getSceneX();
orSceneY = event.getSceneY();
orTranX = ((HBox)(event.getSource())).getTranslateX();
orTranY = ((HBox)(event.getSource())).getTranslateY();
Tabela 40: Pozițiile inițiale ale piesei în scenă
Dacă, utilizatorul mută mouse-ul din poziția inițiala, obiectul de tip "HBox" își modifică
translațiile x si y în funcție de poziția actuală a mouse-lui.
Odată ce piesa este mișcată de pe tabla sursă ea nu poate fi pusă decât într-un loc liber din
tabelul destinație. Dacă obiectul de la linia i și coloana j din tabelul destinație ,unde a fost
lăsat acel obiect nu conține nici un alt element atunci mutarea este corectă, iar în tabelul
destinație este adăugat la linia i și coloana j obiectul care a fost inițial mutat. În tabelul inițial
va fi adăugat un nou obiect "HBox" gol în locul din care a fost eliminată piesa. Totodată se
vor actualiza și cele două liste care conțin obiectele din cele două tabele. Următoarele
imagini, din Figura 27 de mai jos, pun în evidență acest fapt. Prima reprezintă locul în care a
fost lăsat obiectul iar următoarele două tabelul destinație, respectiv tabelul sursă după ce
piesele au fost așezate corect.
Figura 27: Așezarea unei piese de pe tabla sursă pe tabla destinație
Dacă mutarea nu este corectă, în sensul în care locul în care a fost lăsat obiectul nu are vreo
semnificație, sau locul de pe tabla destinație este ocupat obiectul revine la translațiile inițiale,
ceea ce îl aduce în poziția de unde a fost mutat. Imaginile din Figura 28 prezintă exemple de
mutări incorecte.
O mutare din tabelul destinație în tabelul destinație este permisă doar dacă locul în care este
lăsată piesa se află tot în tabelul destinație. Dacă această condiție nu este îndeplinită piesa
revine în locul în care se afla inițial în tabelul sursă. În prima imagine este exemplificat modul
în care această condiție nu este satisfăcută, iar în a doua imagine locul în care aceasta piesă
revine la poziția inițială.
Imaginile următoare exemplifică cele două cazuri specificate mai sus, primul pentru cazul în
care locul unde este lăsată piesa este liber și al doilea în care cele două piese sunt
interschimbate.
Figura 30: Cazul in care locul în care este lăsată piesa este liber
Piesele care pot fi selectate sunt doar cele din tabelul destinație. Când utilizatorul apasă pe o
astfel de piesă aceasta este reținuta, iar opacitatea piesei devine 0.5. Această facilitate poate fi
oferită utilizând următorul mecanism: atunci când se apasă pe o piesă se reține o variabilă
"selectat", care își schimba valoarea în momentul în care piesa este mișcată.
box.setOnMouseClicked(new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) { selected = true; ...}
}
box. setOnMouseDragged (new EventHandler<MouseEvent>() {
public void handle(MouseEvent event) { selected = false; ...}
}
Tabela 42: Algoritmul de selectare a unei piese
Figura 32: Exemplu de piesa selectată
În momentul în care o piesă este selectată utilizatorul are la dispoziție două variante pentru a
putea roti o piesă: folosind butoanele disponibile pe tablă sau folosind tastatura. În cazul în
care se folosesc butoanele sunt disponibile metodele de rotire stânga și rotire dreapta. În
același mod pot fi folosite și săgețile stânga și dreapta de pe tastatură. Săgeata stânga pentru
rotire stânga și săgeata dreapta pentru rotire dreapta. Secțiunea de cod aflata in Tabela 43
prezintă modul în care au fost captate evenimentele pentru cele două săgeți de la tastatură.
TablePiece.getInstance().getPieceById(c.getId()).RotatePieceRight();}
}
event.consume();}});
Tabela 43: Algoritmul permite rotirea unei piese din tastatura
Această facilitate este disponibilă doar pentru piesele care se află în tabelul destinație.
O piesă se poate aduce în poziția inițială folosind două metode : se poate face dublu-click pe
piesă, iar aceasta revine la poziția inițială sau se poate scoate în afara tablei destinație și
aceasta revine la poziția inițială în tabelul sursă.
Dacă la un moment dat poziția pieselor pe tablă nu este corectă și utilizatorul dorește resetarea
pieselor pe poziția lor inițială se scot toate piesele din tabelul destinație și se repun în pozițiile
lor inițiale în tabelul sursă. În imaginea din Figura 33 de mai jos este exemplificat acest lucru,
în stânga este tabelul destinație, iar în dreapta tabelul sursă.
Piesele vor fi inițial scrisă într-un fișier având forma prezentată în Figura 34.
Odată apăsat butonul start se încarcă folosind o clasa EternityIILoader datele dintr-un
fișier ales, ca mai apoi cu ajutorul acesteia să se afle instanța problemei Eternity2 astfel
definită, dimensiunea problemei, precum și o soluție inițială.
et = new EternityIILoader(filename);
if(!et.createProblem()){
System.out.println("Eroare creare problema");
return;
}
Problem problemEternityII = et.getProblem();
Solution initialSolution = et.getLoadedSolution();
int size = et.getProblemSize();
Tabela 45: Modul de creare a unei instanțe Eternity 2
Dacă pe tabla sursă există încă piese acestea se șterg, iar din piesele așezate pe tabla
destinație, daca există se va crea o soluție inițială. Pentru soluția inițială, din piesele așezate
pe tablă se acceptă doar cele care nu sunt în conflict. Nu se poate accepta o piesa de pe
bordură care nu este așezată corect pe bordura, la fel nu se accepta piese alăturate care nu au
aceleași culori pentru stânga-dreapta și sus-jos. Piesele de pe tabla destinație care se află în
conflict nu vor fi luate în considerare în construcția soluției. În imaginea Figura 35 este
prezentată o tablă de dimensiune 3 x 3 în care sunt așezate o parte din piese.
Figura 35: Tabla de dimensiune 3x3 în care sunt așezate doar o parte din piese
Dintre aceste piese sunt alese doar cele care nu sunt în conflict, Figura 36 de mai jos
prezentând piesele ce vor face parte din soluția inițială.
Figura 36: Tabla de dimensiune 3x3 în care piesele așezate sunt corect poziționate
Dacă pe tabla destinație nu exista nici o piesă soluția nu va avea setate variabilele pentru
identificatori.
Odată creată această primă soluție în care sunt inițializați doar identificatorii pieselor din
soluție, aceasta trebuie trimisă workerului prin intermediul metodei setIntermediareSolution
disponibilă în clasa ExternUtilization. Această metodă va prelua soluția și o va seta, în așa fel
încât în algoritmul genetic în partea de inițializare, toți indivizii conțin acea parte a soluției
deja setate. Celelalte variabile pentru identificatori nesetate vor fi inițializate aleator.
Următoarea secțiune de cod din cadrul Tabelei 47 prezintă modul în care se poate înregistra
un eveniment.
După această etapă se definește un worker care va fi setat în cadrul solverului OmniCS.
După ce solverul a fost setat corespunzător se definește un Thread prin care solverul va fi
pornit și după caz va putea fi oprit. Pentru aceasta a fost utilizată o clasă SolverThread.
@Override
public void run() {
solver.findSolution();
solver.printStatistics();
}
}
Tabela 49
Cu ajutorul handlerul înregistrat în cadrul algoritmului se apelează metoda print care va primi
ca parametri soluția cea mai bună în cadrul unei iterații a algoritmului genetic. În prima etapă
se vor șterge toate piesele așezate de pe tabla destinație în iterația de dinainte, apoi se va
parcurge soluția primită și se vor selecta piesele în ordinea identificatorilor. Piesele vor putea
fi identificate după id fiecăreia. Acestea vor fi rotite după variabilele orientare iar canvasul
acestora va fi așezat pe tablă la linia și coloana specificate în identificator. Se știe că linia =
val / size iar coloana = val % size. Pe parcursul afișării utilizatorul nu are acces la piese, deci
nu poate face nici o schimbare.
5.10. Oprirea temporară a algoritmului
Dacă la un moment dat algoritmul nu reușește să ajungă spre soluția finală solverul va putea fi
pus într-un mod de pauza, mod în care utilizatorul va putea face schimbări la soluția afișată.
Odată cu trecerea algoritmului în modul pauză se redă utilizatorului posibilitatea de a muta
piesele. Acesta va putea roti, muta sau scoate unele din piese care pot fi așezate incorect.
Pentru a nu se pierde de obicei soluția generată în acest mod, trebuie să fie cea mai bună, deci
scorul maxim trebuie să fie egal sau mai mare cu cel actual. Algoritmul se pune în modul
pauză utilizând metoda pause() existentă în clasa ExternUtilization. Odată pus în modul de
pauză , solverul mai are posibilitatea de a fi repornit sau de a fi oprit de tot.
După ce au fost aduse toate modificările necesare și soluția a rămas cea mai bună se poate
reporni solverul. Într-o primă etapă se verifică dacă soluția astfel generată conține toate
variabilele identificator setate, iar dacă nu acestea se setează utilizând piesele care încă se află
în tabla sursă. După construcția soluției, aceasta se trimite algoritmului iar acesta găsește și
setează noua soluție în locul celei mai bune soluții găsite până în momentul respectiv.
ExternUtilization.getInstance().setIntermediarBestSolution(best);
Tabela 50: Modul de setare a soluțiilor intermediare
Apoi se va suspenda din nou controlul utilizatorului asupra tablei, iar algoritmul se va scoate
din starea de pauză și va putea continua rezolvarea problemei.
Algoritmul poate fi oprit în orice moment cu ajutorul metodei stop() disponibilă în clasa
Table. Odată oprit algoritmul nu mai poate continua de unde a ajuns. Această etapă este
utilizată în momentul în care interfața va fi închisa, pentru a putea opri și algoritmul.
Daca nu este oprit, algoritmul se va opri în momentul în care funcția fitness va avea valoarea
egala cu 0.0 deci când toate constrângerile definite în problemă sunt satisfăcute. În momentul
în care toate constrângerile vor fi satisfăcute tabla destinație își va schimba opacitatea, piesele
nu vor mai putea fi modificate iar algoritmul nu va mai putea fi pornit decât în cazul încărcării
unui nou fișier.
Figura 37: Tabla de dimensiune 5x5 având toate piesele așezate corect
6. Rezultate obținute
Principalul obiectiv al acestor teste a fost de a vedea dacă timpul obținut de acest
algoritm este comparabil cu timpul obținut de un algoritm de căutare sistematică.
Pentru timpii obtinuți în tabelul de mai sus a fost efectuat Graficul 11 de mai jos. Este evident
faptul că timpul de căutare a unei soluții crește exponențial în raport cu dimensiunea tablei.
Scopul lucrării a fost de a formula o metodă care utilizează tehnici evolutive pentru a
rezolva probleme de satisfacere a constrângerilor (CSP) în cadrul solverului OmniCS. Pentru
aceasta am modelat un algoritm genetic astfel încât să fie capabil să ofere soluții optime (sau
foarte aproape de punctul de optim) al instanțelor CSP, în cadrul solverului OmniCS.
𝑉 = {𝑉1 , 𝑉2 , … , 𝑉𝑛 }
𝐷𝑖 = {1, … , 𝑛}
Algoritmii genetici sunt utili atunci când spațiul de căutare este mare, aceștia
furnizând o soluție, nu neapărat optimă, într-un timp mult mai scurt decât orice algoritm de
căutare sistematică. Pentru aceasta am realizat o comparație între studiul realizat de Stuart
Cam. privind rezolvarea puzzle-ului Eternity 2 prin backtracking și algoritmul genetic propus
de mine. Au fost considerate doar cazurile în care cei doi algoritmi ajung la o soluție finală,
iar rezultatele obținute sunt prezentate în Graficul 12 de mai jos.
Graficul 12:Comparație între AG si Backtracking
O altă soluție ar putea fi atașarea altui alt algoritm (de exemplu rețelele neuronale..)
care să supervizeze funcționarea, modificând parametrii, cromozomi din populație, diferiți
operatori de mutație,încrucișare sau selecție în funcție de evoluția algoritmului genetic.
Odată realizate aceste îmbunătățiri s-ar putea obține rezultate bune, chiar și pentru o
instanță a problemei Eternity 2 de dimensiune 16x16, care să conțină piesele originale ale
puzzle-ului disponibil în comerț.
Bibliografie
[1] Abdullah Konak, David W. Coit, Alice E. Smith, "Multi-Objective Optimization Using
Genetic Algorithms".
[2] Papa Ousmane Niang, "Solving the Eternity II Puzzle using Evolutionary Computing
Techniques", 2010.
[3] Pierre Schaus, Yves Deville, "Hybridization of CP and VLNS for Eternity II", 2008.
[4] Jorge Munoz, German Gutierrez, and Araceli Sanchis, "Evolutionary Genetic
Algorithms in a Constraint Satisfaction Problem: Puzzle Eternity II".
[6] Mark Logan, Dr. D.A.Harrison (coord.), "Genetic Algorithm - Eternity II", University
of Northumbria at Newcastle, 2008.
[7] Carlos ANSÓTEGUI, Ramon BÉJAR, Cèsar FERNÁNDEZ, and Carles MATEU, "
How Hard is a Commercial Puzzle: the Eternity II Challenge", Dept. of Computer Science,
Universitat de Lleida, SPAIN.
[8] Cristian Frăsinaru, Prof. Dr. Elefterie Olaru (coord.), "Probleme algoritmice în studiul
proprietăţilor ereditare pe grafuri", Facultatea de Informatică, Universitatea ”Al. I. Cuza”
Iaşi.
[10] Erik D. Demaine, Martin L. Demaine, "Jigsaw Puzzles, Edge Matching, and
Polyomino Packing: Connections and Complexity", MIT Computer Science and Artificial
Intelligence Laboratory, USA, 2006.
[12] Daniel Hunter Frost, "Algorithms and Heuristics for Constraint Satisfaction
Problems", University of California, Irvine, 1997.
[13] Edward Tsang , Christos Voudouris, "Constraint Satisfaction in Discrete
Optimisation", 1998.
[15] Wim Vancroonenburg, Tony Wauters, Greet Vanden Berghe, "A two phase hyper-
heuristic approach for solving the Eternity II puzzle", CODeS research group, Belgia.
[16] Edmund Burke, Jim Newall Graham Kendall and, Emma Hart, Peter Ross, Sonia
"Schulenburg.Hyper-Heuristics: An Emerging Direction in Modern Search Technology",
capitolul 16, pagina 457–474, Springer New York, 2003.
[18] Dechter, R., "Constraint Processing". Morgan Kaufmann, San Francisco (2003).
[22] John H. Holland, "Genetic Algorithms and Adaptation", 1984, Springer US, paginile
314-333.