Sunteți pe pagina 1din 68

UNIVERSITATEA ALEXANDRU IOAN CUZA IAŞI

FACULTATEA DE INFORMATICĂ

LUCRARE DE LICENŢĂ

Utilizarea Algoritmilor Genetici

pentru rezolvare Problemelor de

Satisfacere a Constrângerilor

propusă de

Irinel Alexandru Bogdan

Sesiunea: iulie, 2014

Coordonator ştiinţific

Lector, Dr. Cristian Frăsinaru

UNIVERSITATEA ALEXANDRU IOAN CUZA IAŞI


FACULTATEA DE INFORMATICĂ

Utilizarea Algoritmilor Genetici

pentru rezolvare Problemelor de

Satisfacere a Constrângerilor

Irinel Alexandru Bogdan

Sesiunea: iulie, 2014

Coordonator ştiinţific

Lector, Dr. Cristian Frăsinaru


DECLARAŢIE PRIVIND ORIGINALITATE ŞI RESPECTAREA
DREPTURILOR DE AUTOR

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;

- rezumarea ideilor altor autori precizează referinţa precisă la textul original.

Iaşi, data

Absolvent Irinel Alexandru Bogdan

(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ă.

De asemenea, sunt de acord ca Facultatea de Informatică de la Universitatea Alexandru Ioan


Cuza Iaşi să utilizeze, modifice, reproducă şi să distribuie în scopuri necomerciale
programele-calculator, format executabil şi sursă, realizate de mine în cadrul prezentei lucrări
de licenţă.

Iaşi, data

Absolvent Irinel Alexandru Bogdan

(semnătura în original)
Cuprins

Introducere .................................................................................................................. 3

Generalităţi ................................................................................................................. 4

Modelul CSP ................................................................................................. 5

Algoritmi Genetici ........................................................................................ 5

Solverul OmniCS ........................................................................................................ 5

Interacţiunea cu solverul OmniCS ............................................................. 5

Problemă ....................................................................................................... 5

Worker .......................................................................................................... 5

Soluţie ............................................................................................................ 5

Evaluarea constrângerilor ........................................................................... 5

Evaluarea unei anumite constrângeri ........................................................ 5

Selectarea worker-ului ................................................................................. 5

Modelarea CSP a problemei Eternity 2 ................................................................... 7

Descrierea problemei de "edge-matching" ................................................ 5

Modelul formal ............................................................................................. 5

Puzzle-lul Eternity 2 .................................................................................... 5

Modelul CSP al puzzle-ulul Eternity 2 ...................................................... 5

Rezolvare problemei Eternity 2 prin Algoritmi Genetici ...................................... 7

Reprezentarea internă a soluţiei ................................................................ 5

Generarea bordurii ..................................................................................... 5

Pasul de iniţializare al Algoritmului Genetic ........................................... 5

Funcţia de evaluare ..................................................................................... 5

Operatorul de selecţie ................................................................................. 5


Operatorul de mutaţie ................................................................................ 5

Operatorul de încrucişare .......................................................................... 5

Algoritmi de reparare a soluțiilor ............................................................. 5

Interfaţa aplicaţiei problemei Eternity 2 ................................................................ 8

JavaFX ......................................................................................................... 5

Piesa .............................................................................................................. 5

Colorarea pieselor ....................................................................................... 5

Rotire simplă a unei piese ........................................................................... 5

Tabla sursă și tabla destinaţie .................................................................... 5

Încărcarea pieselor din fişier ...................................................................... 5

Rezolvarea prin Algoritmi Genetici ........................................................... 5

Pornirea algoritmului .................................................................................. 5

Afişarea soluţiilor parţiale .......................................................................... 5

Oprirea temporară a algoritmului ............................................................. 5

Repornirea algoritmului ............................................................................. 5

Oprirea algoritmului .................................................................................. 5

Terminarea algoritmului ............................................................................ 5

Rezultate obţinute ..................................................................................................... 5

Concluzii .................................................................................................................... 5
Introducere

Raționamentul bazat pe constrângeri este o paradigmă simplă și foarte puternică, cu ajutorul


căreia pot fi formulate și rezolvate multe probleme interesante.

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.

O problemă de satisfacere a constrângerilor constă din o mulțime finită de variabile, pentru


fiecare variabilă o mulțime finită de valori reprezentând domeniul acesteia, și o mulțime de
constrângeri, fiecare introducând restricții asupra valorilor pe care le pot avea variabilele.

O soluție a unei probleme de satisfacere a constrângerilor reprezintă o asignare de valori


pentru fiecare variabilă astfel încât toate constrângerile să fie satisfăcute. În funcție de
problema de rezolvat trebuie specificată, fie o soluție oarecare, fie una optimă, fie toate
soluțiile problemei.

Aplicațiile practice ale problemelor de satisfacere a constrângerilor sunt nelimitate, plecând


de la probleme de puzzle simple (problema damelor, problema reginelor, problema eternity),
modelarea unui orar, ajungând până la controlul traficului aerian, de metrou sau de căi ferate.

Soluțiile unei CSP se pot obține prin parcurgerea sistematică a spațiului de


căutare(Backtracking), dar și utilizând diferite euristici atunci când spațiul de căutare este
mare.

Creșterea complexității sistemelor concepute în ultimele decenii a impus, în proiectarea


acestora, utilizarea aproape exclusivă a calculatoarelor. Un număr mare de metode de calcul
fiind dezvoltate și implementate, dar toate aceste activități stau sub semnul maximizării
performanțelor, iar intrarea lor sub incidența optimizării este inerentă, conducând la apariția
unor algoritmi de proiectare din ce în ce mai sofisticați.

"Optimizarea reprezintă activitatea de selectare, din mulțimea soluțiilor posibile unei


probleme, a acelei soluții care este cea mai bună în raport cu un criteriu predefinit. Această
definiție implică existența următoarelor componente:

 problemă tehnică constând în calculul matematic al unei soluții;


 Existența mai multor soluții pentru aceeași problemă;
 Un criteriu de selectare a soluției optime." [13]
Algoritmii genetici au fost propuși de John Holland în 1973, aceștia "modelând moștenirea
genetică și lupta Darwiniana pentru supraviețuire." [22] Acești algoritmi au fost aplicaţi cu
succes într-o varietate de aplicaţii care necesită optimizarea globală a soluţiei fiind des folosiți
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 pornesc de la o mulţime iniţială de soluţii numită populaţie. În această


populaţie fiecare individ este numit cromozom şi reprezintă o soluţie posibilă a problemei.
Aceşti cromozomi evoluează pe durata iteraţiilor succesive numite generaţii. În fiecare
generaţie, cromozomii sunt evaluaţi utilizând o funcție de evaluare(fitness). Pentru crearea
următoarei populaţii cei mai buni cromozomi din generaţia curentă sunt selectaţi şi noii
cromozomi sunt formaţi folosind unul dintre cei trei operatori genetici esenţiali: selecţia,
încrucișarea şi mutaţia".

Lucrarea încearcă să prezinte o alternativă a folosirii algoritmilor genetici în rezolvarea


problemelor de satisfacere a constrângerilor, precum și modelarea unor probleme ca și
probleme CSP.

Astfel, primul capitol abordează aspecte teoretice cu privire la conceptele de problemă de


satisfacere a constrângerilor, problema de optimizare, respectiv algoritmi genetici.

În al doilea capitol este prezentată interacțiunea cu solverul OmniCS (de rezolvare a


instanțelor CSP) realizat de către domnul Lector, Dr. Cristian Frăsinaru în cadrul tezei
"Probleme algoritmice în studiul proprietăților ereditare pe grafuri", privind utilizarea acestuia
în scopul rezolvării problemelor CSP cu ajutorul algoritmilor genetici. În acest capitol de
altfel sunt definite concepte ca problema, solver, worker, soluție și necesitatea utilizării de
strategii de rezolvare diferite în funcție de problema CSP aleasa. Tot aici fiind prezentat și un
algoritm genetic de rezolvare a problemelor CSP generale (problema reginelor, problema
rucsacului, problema pătratului magic, acestea fiind probleme care nu necesită ca unele
variabile să rămână neschimbate pe parcurs).

Î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.

Ultimul capitol prezintă performanțele obținute de algoritmul genetic prezentat în capitolul


patru în rezolvarea problemei Eternity 2 pentru dimensiuni diferite, cât și o comparație cu un
algoritm Backtracking de parcurgere sistematica a spațiului soluțiilor.
1. Generalități

1.1. Modelul CSP

O problemă de satisfacere a constrângerilor (CSP) constă din:

 o mulțime finită de variabile 𝑉 = {𝑣1 , 𝑣2 , … , 𝑣𝑛 }


 pentru fiecare variabilă 𝑥𝑖 o mulțime finită 𝐷𝑖 de valori posibile (domeniul acesteia)
 o mulțime finită de constrângeri, fiecare introducând restricții asupra valorilor pe care
anumite variabile le pot lua simultan

O soluţie a unei probleme de satisfacere a constrângerilor este o atribuire < 𝑣1 = 𝑥1 >< 𝑣2 =


𝑥2 > ⋯ < 𝑣𝑛 = 𝑥𝑛 >, 𝑥𝑖 ∈ 𝐷, 𝑣𝑖 ∈ 𝑉, 1 ≤ 𝑖 ≤ 𝑛 a câte unei valori fiecărei variabile (din
cadrul domeniului său) astfel încât toate constrângerile existente să fie satisfăcute. Scopul este
obținerea:

 unei soluții oarecare fără vreo preferință anume


 tuturor soluțiilor
 unei soluții optimale bazându-se pe un anume criteriu

.Soluțiile unei probleme de satisfacere a constrângerilor se pot obține sistematic, prin


parcurgerea tuturor spațiilor soluțiilor, dar și utilizând diverse euristici, atunci când spațiul de
căutare este foarte mare.

Există astfel mai multe tipuri de constrângeri declarative, aditive, ne-direcționare(X = Y +


1,Y=X - 1), eterogene(X = size(X)), care specifica informații parțiale(X>2).

Exemple de probleme modelate CSP:

Problema celor n regine

𝑉 = {𝑅1 , 𝑅2 , … , 𝑅𝑛 }

𝐷𝑖 = {1, … , 𝑛}

𝐶𝑣𝑖 𝑣𝑗 = {𝑅𝑖 ≠ 𝑅𝑗 ∧ |𝑖 − 𝑗| ≠ |𝑅𝑖 − 𝑅𝑗 |, ∀ 𝑖 ≠ 𝑗}

Problema SEND + MORE = MONEY

𝑉 = {𝑆, 𝐸, 𝑁, 𝐷, 𝑀, 𝑂, 𝑅, 𝑌}

𝐷𝑖 = {0, … ,9}
𝐶 = {1000 ∗ 𝑆 + 100 ∗ 𝐸 + 10 ∗ 𝑁 + 𝐷 + 1000 ∗ 𝑀 + 100 ∗ 𝑂 + 10 ∗ 𝑅 + 𝐸
= 10000 ∗ 𝑀 + 1000 ∗ 𝑂 + 100 ∗ 𝑁 + 10 ∗ 𝐸 + 𝑌}

1.2. Algoritmi genetici

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.

Algoritmii genetici pornesc de la o mulţime iniţială de soluţii numită populaţie. În


această populaţie fiecare individ este numit cromozom şi reprezintă o soluţie posibilă a
problemei. Aceşti cromozomi evoluează pe durata iteraţiilor succesive numite generaţii. În
fiecare generaţie, cromozomii sunt evaluaţi utilizând unele măsuri de potrivire (fitness).
Pentru crearea următoarei populaţii cei mai buni cromozomi din generaţia curentă sunt
selectaţi şi noii cromozomi sunt formaţi folosind unul dintre cei trei operatori genetici
esenţiali: selecţia, încrucișarea şi mutaţia.
Selecţia asigură că anumiţi cromozomi din generaţia curentă sunt copiaţi în acord cu
valoarea funcţiei fitness în noua generaţie, iar aceasta presupune că cromozomii cu o valoare a
funcției fitness mare au o probabilitate mare să contribuie la formarea noii generaţii.
Încrucișarea este un alt operator genetic care reprezintă procesul prin care pe baza a doi
cromozomi din populaţia curentă sunt formaţi doi cromozomi pentru populaţia următoare.
Mutaţia este procesul prin care un cromozom din populaţia curentă este modificat şi salvat în
noua populaţie.

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

Figura 1: Schema generală a unui Algoritm Genetic

1.2.2. Funcția de evaluare

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.

1.2.3. Operatorul de selecție

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.

1.2.5. Operatori de încrucișare

Operatorul de încrucișare este aplicat la o pereche de părinţi aleşi aleator. Cu o probabilitate


pc părinţii sunt recombinaţi pentru a forma doi noi copii care vor fi introduşi în noua
populaţie. De exemplu luăm 2 părinţi din populaţia curentă, îi împărţim şi încrucişăm
componentele astfel încât să producem 2 noi candidaţi. Aceşti candidaţi în urma încrucişării
trebuie să reprezinte o soluţie posibilă pentru parametrii problemei noastre de optimizare de
aceea de obicei se interschimbă valori de pe aceleaşi poziţii.
2. Solverul OmniCS

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].

2.1. Interacțiunea cu Solverul OmniCS

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();

Tabela 3: Modul de utilizare a solverului OmniCS fără atașarea unui "worker"


2.2. Problemă

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 class NewProblem extends Problem{


public NewProblem(){

}
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.

Var x = variable("X", 0, 9);


Var y = variable("Y", 0, 9);
Var z = variable("Z", 0, 9);
Tabela 5: Modul de definire a variabilelor

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.

public class NewConstraint extends Constraint{


public NewConstraint(){}
public int eval(State arg0){}
}
Tabela 6: Exemplu de derivare al clasei Constraint

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.

public class EternityConstraintAdiacent extends Constraint{


public EternityConstraintAdiacent(Var vara,Var varb,String expr){
this.vara = vara;
this.varb = varb;

}
@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.

public class EternityII extends Problem{


private int size;
public EternityII(int n, int colorSize){
this.size = n;
for(int i=0;i<n*n;i++)
up[i] = this.variable("U"+i, 0, colorSize);
...
for(int j=0;j<n-1;j++)
for(int i=0;i<n-1;i++)
this.post(new
EternityConstraint1x1(this,identifier[n*j + i],identifier[n*j + i+1]);
this.postAllDiff(identifier);

}
}

Tabela 8: Definirea clasei problemei Eternity 2


Diagrama 1: Modul de definire al claselor în cadrul problemei Eternity 2

Pentru celelalte tipuri de probleme, cele deja modelate in cadrul OmniCS, au fost utilizate
cele prezente în pachetul Omnics.Sample.

2.3. Worker

Pentru a putea implementa un algoritm de rezolvare a problemelor diferit de cele prezente în


pachetul OmniCS este nevoie să se creeze o nouă clasă care extinde clasa "worker" din
pachetul omnics.solver.worker.

Tabela 9 prezintă modul în care este definită o clasă care extinde clasa Worker, și metodele
care trebuiesc implementate.

public class NewWorker extends Worker{


public AlgorithmWorker(Solver solver, Problem problem){
}
protected void solve() {
}
}
Tabela 9: Exemplu de derivare a clasei Worker

Î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.

În Tabela 11 este exemplificat modul de pornire a solverului pentru o problemă și un


"worker".

Problem problem = new NewProblem();


Solver solver = new Solver(problem,false);
Worker wk = new AlgorithmWorker(solver,problem);
solver.addWorker(wk);
solver.findSolution();

Tabela 11: Modul de pornire a solverului

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ă.

Solution solution = new Solution(problem);


solution.set(index,value_from_domain);
solution.set(variable,value_from_domain);
solution.get(index);
solution.get(variable);
solution.îșinstantied(variable);
Tabela 12: Diferite metode ale clasei Solution

2.5. Evaluarea constrângerilor

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.

public boolean evalSolution(Solution sol){


State st = new State(this.best);
for(Constraint key:this.problem.constraints())
if(key.eval(st) != 1)
return false;
return true;
}
Tabela 13: Funcție care permite evaluarea unei soluții pentru o problema

2.6. Evaluarea unei anumite constrângeri

Pentru a evalua doar o anumită constrângere se verifica dacă numele constrângerii


respectă un anumit pattern, iar cu ajutorul metodei constraint.eval(state) dacă constrângerea
astfel găsită este satisfăcută sau nu.

public boolean evalConstraint(Constraint cons,State st){


for(Constraint key:this.problem.constraints())
if(key.getClass().equals(Cons.Class) && key.eval(st) == 1)
return true;
return false;
}
Tabela 14: Funcție care permite evaluarea unei singure constrângeri ale soluției pentru o problemă

2.7. Selectarea Worker-ului

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ță.

public interface AbstractCreateWorker {


public Worker create(Problem prb,String problemName,String
borderFile,Solver solver,long workingtime,int popsize,double
alphaSelectProbability);
}
Tabela 15: Modul de definire a clasei AbstractCreateWorker

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 class DefaultWorker extends Worker{


public DefaultWorker(Solver solver, Problem problem,long workingtime,int
popsize,double alphaSelectProbability) {
super(solver, problem);
...
}
@Override
protected void solve() {
...

}
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

Operatorul de încrucișare este aplicat la o pereche de părinți aleși aleator. Cu o


probabilitate pc părinţii sunt recombinaţi pentru a forma doi noi copii care vor fi introduşi în
noua populaţie. De exemplu luăm 2 părinţi din populaţia curentă, îi împărţim şi încrucişăm
componentele astfel încât să producem 2 noi candidaţi. Aceşti candidaţi în urma încrucişării
trebuie să reprezinte o soluţie posibilă pentru parametrii problemei noastre de optimizare de
aceea de obicei se interschimbă valori de pe aceleaşi poziţii. Utilizând un punct de
recombinare putem crea noii candidaţi prin combinarea primei părţi din primul părinte cu a
doua parte din al doilea părinte. Dacă dimensiunea cromozomului este mare se pot alege mai
multe puncte de recombinare. In Tabela 18 de mai jos este prezentat un exemplu cu trei
puncte de tăiere pentru problema reginelor:

Tabela 18: Exemplu de aplicarea a operatorului de încrucișare

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.

In Tabela 19 de mai jos avem un exemplu de funcție de evaluare(fitness) pentru problema


Patratului Magic.

public class MagicSquareFitness extends FitnessFunction{


@Override
public double eval(Solution sol) {
...
for(Constraint ct : prb.constraints()){
if(ct.eval(st) == 1)
satisfied ++;
}
return 1-satisfied/totalCons;
}
}
Tabela 19: Funcția de evaluare(fitness) în cazul problemei Pătratului Magic

Pentru problema rucsacului este utilizată următoarea funcție.

public class KnapsackProblemFitness extends FitnessFunction{

@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.

public interface AbstractFitnessFunctionSelect {


AbstractFitness create(Problem prb);
}
Tabela 21: Interfața AbstractFitnessFunctionSelect
Diagrama 3: Selectarea funcției de evaluare(fitness) în funcție de problemă

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.1. Descrierea problemei de "edge-matching"

O problemă de "edge-matching" este o problemă de dimensiune 𝑛 𝑥 𝑛 avand c culori


unde avem de așezat o mulțime de piese pe o tablă, urmând o regulă simplă. Piesele au patru
fețe, fiecare colorată într-un anume fel, respectând un anumit tipar. Regula reprezintă faptul
că două piese pot fi puse una lângă alta doar dacă laturile adiacente au aceeași culoare.
Laturile pieselor de pe extremitățile tablei au întotdeauna aceeași culoare c0 [3].

3.2. Modelul formal

Definiție: O problemă de "edge-matching" având dimensiune 𝑛 𝑥 𝑛 și c culori reprezintă un


cuplu ((𝑉, 𝑆) , unde 𝑉 = {𝑣𝑖,𝑗 |𝑖 ∈ [1, 𝑛], 𝑗 ∈ [1, 𝑛]} reprezintă o mulțime de variabile
reprezentând pozițiile pe tablă, 𝑆 = {(𝑡, 𝑟)|𝑡 ∈ 𝑇, 𝑟 ∈ 𝑅} domeniul pentru V , unde 𝑅 =
{0°, 90°, 180°, 270°} este multimea posibilelor rotatii ale piesei, iar 𝑇 = {(𝑥1 , 𝑥2 , 𝑥3 , 𝑥4 )|𝑥𝑖 ∈
𝐶, 𝑖 ∈ [1,4]} multimea culorilor 𝐶 = {𝑐𝑖 |𝑖 ∈ [1, 𝑐]} laturilor piesei . Pentru ∀ 𝑣 ∈
{𝑣1,𝑖 , 𝑣𝑛,𝑖 |𝑖 ∈ [1, 𝑛]} ∪ {𝑣𝑗,1 , 𝑣𝑗,𝑛 |𝑗 ∈ [1, 𝑛]} oricare 𝑡𝑖 ∈ 𝑇, 𝑡𝑖 fața exterioară => 𝑡𝑖 = 𝑐0
,culoarea pentru bordură, 𝑖 ∈ [1,4] [3]. Modelul este ilustrat în Figura 2 de mai jos.

Figura 2: Tabla Eternity 2 de dimensiune 4x4, cu piesele așezate corect [21]

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).

Dimensiunea spaţiului tuturor soluţiilor pentru o tabla de dimensiune 16 x 16, este de


256! 4256 combinatii.

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.

Problema se încadrează în clasa problemelor NP-completă. Se știe ca Eternity 2, și Tratavex


se încadrează în clasa problemelor de "edge-matching". În particular Tratavex a fost
demonstrată ca fiind "NP-completă prin reducerea lin3-SAT[1]" [9]. Erik Demaine și Martin
Demaine au arătat în 2007 că "orice problemă din clasa "edge-matching" este NP-Completă"
[10]. Antoniadis și Lingas au arătat în 2010 că "problema este APX-completa" [23], deci că
problemele din "clasa de probleme "edge-matching" nu admit algoritmi de aproximare în
timp polinomial decât în cazul P = NP" [23].

În timp s-au încercat diferite metode de rezolvare a problemelor de "edge-matching".


Rezultate privind rezolvarea problemei Eternity 2 sunt prezente în Tabela 22 de mai jos.

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

Monuz at al.(2009) a folosit un algoritm genetic și un algoritm evolutiv multiobiectiv.


Utilizând operatori simpli de mutație și încrucișare , și o funcție cu mai multe obiective
grupate , a reușit să realizeze un scor de 391/480 .

3.4.Modelul CSP al puzzle-ului Eternity 2


Problema Eternity 2 modelată ca o instanța CSP:

Variabile : Pentru fiecare poziție de pe tablă 16 x 16 definim următoarele variabile

 Ui j , Ri j , L i j , Di j ∈ {0, … , 𝑐} reprezintă colorile pentru pozițiile sus, jos, stânga,


dreapta pentru piesa {i,j}
 Ii j ∈ {0, … , 𝑛2 − 1 } reprezintă identificatorul piesei care se află la acea poziție {i,j}
 Oi j ∈ {0, ..., 3} reprezintă orientarea piesei {i,j}
Constrângerile :
 Toate variabilele Ii j trebuie să fie diferite, nu poate fi o piesă in 2 locuri în același
timp, deci o constrângere AllDiff pentru variabilele Ii j
 Constrângerile pentru edge-matching se reprezintă simplu prin : Di j = Ui-1 j , Ui j = D i-
1j ,
Li j = Ri j-1 , Ri j = Li j+1, pentru fiecare piesă 1 x 1. Figurile 1,2,3,4 ilustrează acest
lucru.
 Se mai adaugă și un set de constrângeri pentru porțiuni 2 x 2, la fel ca cele de mai sus.
 Bordura trebuie să fie 0, deci este nevoie de o constrângere care satisface această
problemă

Figura 3: Exemple de potriviri (primele două imagini) și nepotriviri (ultimele două imagini) între piese
[21]
4. Rezolvarea problemei Eternity 2 cu Algoritmi Genetici

Algoritmii genetici au fost folosiți cu succes în rezolvarea multor probleme de optimizare


combinatorială.
În Tabela 23 de mai jos este prezentată schema generală a unui algoritm genetic:

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.

4.1.Reprezentarea internă a soluției


Tabla este reprezentată ca un vector de dimensiune nxn prin intermediul identificatorilor. La
adresa identificatorilor se găsește piesa care are atașat un vector de dimensiune 5, acesta
conținând culorile pentru sus, jos, stânga și dreapta incluzând orientarea piesei respective.

Figura 4: Reprezentarea unei piese

De exemplu mulțimea 1, 5, 4, 3, 0 reprezintă piesa din Figura 5 de mai jos :


Figura 5: Reprezentarea piesei 1, 5, 4, 3, 0

Mulțimea 1, 5, 4, 3, 1 reprezintă piesa din Figura 6, iar după cum se poate observa piesa este
rotită.

Figura 6: Reprezentarea piesei 1, 5, 4, 3, 1

Instanța problemei cu care se începe Algoritmul genetic conține inițializate variabilele


U,D,L,R având orientarea O egala cu 0 si Identificatorii I nesetați, aceștia vor fi ulterior setați
la pasul de inițializarea a populației algoritmului genetic.

4.2. Generarea bordurii

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.

Figura 7: Tabla de dimensiune 4x4 cu bordura așezată corect [21]


scoate toate piesele care fac parte din bordură într-o lista
genereazăBordura(new Soluție(),lista,0)
genereazăBordura( soluție, lista,index){
if(funcție_cost(soluție) == n*(n-1)){ //dacă soluția e finală și
corectă
adaugă_soluția(lista_borduri_corecte)
return
}
if(index == max) //s-a ajuns la capăt fără o soluție validă
return
curent <- scoate din listă elementul de pe poziția index
foreach(element în listă){
do{
if(element se potrivește la poziția index + 1)
potrivit <- element
break
else
rotește(element)
}while(i <= 3)
if(potrivit)
sol <- adaugă la soluție potrivit
genereazăBordura(sol,lista,index+1)
}
}
Tabela 24: Algoritmul de generare a bordurii

4.3. Pasul de inițializare al 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.4. Funcția de evaluare


Funcția fitness este utilizată pentru a măsura calitatea cromozomilor. Pentru acest caz funcția
fitness calculează numărul constrângerilor satisfăcută de o soluție în raport cu numărul
constrângerilor totale. Un individ bun obține o valoare fitness apropiata de 0.
Există mai multe obiective a funcției fitness.
1. Primul obiectiv reprezintă găsirea a cât mai multe constrângeri 1 x 1 valide din
interiorul tablei , fără a lua însă în calcul bordura și relația cu bordura [1].
2. Al doilea obiectiv reprezintă găsirea constrângerilor valide referitor la relația dintre
interior și bordură [1].
3. Al treilea obiectiv reprezintă găsirea a cât mai multe constrângeri 2 x 2 valide din
interiorul tablei [1].
Cele 3 obiective sunt ilustrate în Figura 8 de mai jos, cu roșu au fost marcate constrângerile
invalide, iar cu albastru constrângerile valide.

Figura 8: Obiectivele funcției de evaluare [21]

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.

4.5.2. Schema de selecţie utilizând metoda lui Gauss


Aceasta metodă 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). Utilizăm formula lui Gauss pentru a
(𝑀−𝑓𝑖𝑡𝑛𝑒𝑠𝑠(𝑐𝑖))2

calcula probabilitatea ca un cromozom să fie un cromozom bun 𝑃(𝑐𝑖) = 𝑒 2𝛼2 ,
unde P(ci) reprezintă probabilitatea calculată pentru individul ci , M reprezintă media
(valoarea maxima a funcției fitness) , si α reprezintă dispersia adică panta cu care scade
probabilitatea, iar funcția fitness(ci) reprezintă valoarea funcției de evaluat pentru individul ci.
De exemplu dacă valoarea maximă a funcției de evaluat va fi 1, M va fi 1, iar α va fi în
intervalul [0.3,0.5]. Apoi se calculează un număr random în intervalul r ∊ [0,1] și se compară
cu P(ci), dacă P(ci) va fi mai mare decât r cromozomul ci va fi ales și pus în noua populație.

for i = 0 ... POP_SIZE


eval[i] = fitness(P[i])
M <- max_fitness_value
alpha <- 0.35
for i = 0 ... POP_SIZE
p[i] <- pow(e,(M-eval[i])/pow(alpha,2))
current <- 0
while(current < POP_SIZE)
random_chromosome <- random(0,POP_SIZE-1)
r <- random(0,1)
if(p(random_chromosome) > r)
alege individul random_chromosome pentru supraviețuire
current <- current + 1
Tabela 27: Schema de selecție utilizând metoda lui Gauss

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ă.

Diagrama 4: Clasele utilizate pentru diferiți operatori de mutație

4.6.1. Mutație prin rotire


Acest operator alege o genă dintr-un cromozom și o rotește.

Figura 9: Mutație prin rotirea piesei

4.6.2. Mutație prin interschimbare


Această metodă de mutație este una dintre cele mai folosite pentru problemele combinatoriale,
aceasta presupune selectarea unui cromozom aleator, din care vom selecta 2 gene, tot aleator.
Metoda presupune interschimbarea celor două gene.
Figura 10: Mutație prin interschimbarea a două piese

4.6.3. Mutație prin rotire și încrucișare


Această schemă de mutație presupune o combinare între swap mutation și rotate mutation. Se
alege un cromozom și două gene pentru a fi interschimbate, dar după aceasta se încearcă și
rotația pentru fiecare dintre acestea. Prin aceasta rotație se încearcă o poziționare cât mai bună
a piesei referitoare la vecinii acesteia. Soluția se păstrează numai în cazul acesta este mai bună
decât cea anterioară.

Figura 11: Mutație prin rotirea și încrucișarea pieselor

4.6.4. Mutație prin rotirea regiunilor

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

4.6.5. Mutație prin interschimbarea regiunilor


Această schemă este descrisa de Monuz [2]. Aceasta utilizează interschimbarea pe orizontală
și verticală. Se aleg 2 blocuri pe orizontală sau verticală de dimensiuni egale și se
interschimbă între ele. Această schemă are avantajul prevenirii blocajului.

Figura 13: Mutație prin interschimbarea regiunilor 1x2

4.6.6. Mutație prin interschimbare pieselor în regiune


Se selectează o regiune 2 x 2 și se interschimbă piesele pe diagonale fără a realiza nici o altă
modificare. Modificarea se păstrează în cazul în care noul cromozom este mai bun decât cel
inițial selectat.
Figura 14: Mutație prin interschimbarea pieselor în regiune 2x2

4.6.7. Mutație prin interschimbarea a m piese alăturate


Prin această metodă se selectează m gene din cromozomul actual cu condiția ca între acestea
să fie satisfăcute constrângerile. Acestea vor fi adăugate ulterior la o locație aleatoare din
cromozomul curent pentru a crea un cromozom mai bun. Inițial se alege o genă aleatoare, iar
pentru aceasta se încearcă identificarea a două gene din cele rămase, astfel încât să fie
respectate constrângerile între ele. După aceasta piesele vor fi așezate la o locație aleatoare.

selectează un cromozom C aleator


selectează o genă aleatoare
pentru gena selectată generează m piese a.i. constrângerile să fie
satisfăcute
scoate din cromozom piesele selectate
generează aleator o locație l
scoate intr-o lista A piesele din regiunea începând cu locația l
adaugă piesele generate la locația l
umple locurile rămase libere în C cu piese din lista A
Tabela 28: Algoritmul utilizat pentru operatorul de mutație a m piese alăturate

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

4.6.8. Mutație prin crearea unei regiuni mxm corectă


Schema presupune generarea unei regiuni mxm în care toate constrângerile să fie satisfăcute
și așezarea acesteia în cromozomul curent. Se alege o genă dintr-un cromozom aleator, iar
pentru aceasta se generează folosind metoda backtracking o regiune m x m în care toate
constrângerile, mai puțin bordura, sunt satisfăcute. Această regiune este mai apoi adăugată în
noul cromozom la o poziție random în intervalul [1, 𝑛 − 1 − 𝑚], apoi cromozomul este
adăugat în populația curentă. Pentru algoritmul propus au fost utilizate doar regiuni 2x2 sau
3x3.
selectează un cromozom C aleator
selectează o genă aleatoare
pentru genă selectată generează o regiune R mxm corectă
scoate din cromozom piesele din R
generează aleator o locație l
scoate intr-o listă A piesele din regiunea începând cu locația l
adaugă regiunea generată la locația l
umple locurile rămase libere în C cu piese din lista A
Tabela 29: Algoritmul operatorului de mutație a unei regiuni mxm
Figura 16: Exemplu de mutație a unei regiuni 2x2

4.6.9. Mutație prin interschimbarea tuturor pieselor


Dacă la un moment dat se ajunge într-un punct de optim din care nu se mai poate ieși se
încearcă interschimbarea tuturor pieselor astfel încercându-se crearea unei noi soluții care
poate evolua spre optimul global.
4.7. Operatorul de încrucişare
Problemele de optimizare combinatorială sunt probleme complexe, acestea necesitând
ca operatorii, de mutație, de încrucișare să fie foarte atent proiectați. Din această cauză au fost
aleși operatori de încrucișare utilizați cu succes în rezolvarea de probleme complexe, cum ar fi
traveling salesman problem (TSP), capacitated vehicle routing problem (CVRP) sau
scheduling problem (SP). După o analiză atentă a acestor algoritmi se observă că acești
algoritmi de încrucișare sunt făcuți pentru a menține ordinea ceea ce nu este foarte relevant
pentru problema Eternity 2.

Diagrama 5: Clasele utilizate pentru operatorii de încrucișare


4.7.1. Interschimbarea regiunilor
Schema interschimbării regiunilor a fost propusă de Monuz [2] și acesta presupune
următoarele: Sunt selectați 2 cromozomi, iar din fiecare se alege o regiune de dimensiune
fixată. Aceste regiuni sunt interschimbate , ținându-se cont și de posibilitatea de a apărea
duplicate, obținându-se astfel doi noi cromozomi. Menținerea relației dintre piesele adiacente
este esențială dar nu critică, astfel putându-se obține mai multe legături.

selectează o regiune random


copie cei doi cromozomi
scoate din părintele A toate piesele din regiunea B
scoate din părintele B toate piesele din regiunea A
adaugă piesele rămase în ambele regiuni în 2 liste : lista A și lista B
interschimbă cele 2 regiuni
umple locurile ramase libere cu piese din lista A și lista B
Tabela 30: Algoritmul utilizat pentru operatorul de încrucișare prin interschimbarea regiunilor a doi
cromozomi

Figura 17: Exemplu de încrucișare a doi cromozomi prin interschimbarea regiunilor

4.7.2. Încrucișarea uniformă

Î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.

4.7.3. Interschimbare regiune mxm corectă

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.

Figura 19:Exemplu de încrucișare a unei regiuni 2x2

4.7.4. Interschimbarea a m piese alăturate

Î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

4.7.5. Interschimbarea regiunilor corecte

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.

Figura 21: Exemplu de interschimbare a regiunilor 2x2


4.8. Algoritmi de reparare a soluţiilor

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.

4.8.1. Rearanjarea regiunilor 2x2

Prin această metodă se încearcă reașezarea regiunilor 2x2 astfel încât cât mai multe
constrângeri dintre regiuni să fie satisfăcute.

Figura 22:Utilitatea rearanjării regiunilor 2x2

4.8.2 Rearanjarea bordurii

Dacă la o anumită iterație în populația actuală se găsesc cromozomi având valoarea


funcției fitness pentru bordura mare, se va încerca schimbarea identificatorilor pieselor care
alcătuiesc bordura. Aceasta verificare are loc doar pentru un procent de 30% din populație,
cromozomii verificați fiind aleși aleator. În imaginea din Figura 23 se observa felul în care
este realizată această modificare pentru un anumit cromozom.
Figura 23: Utilitatea rearanjării bordurii

4.8.3. Metoda de selectare a algoritmului de reparare

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].

 integrare completă cu Java SE și JDK, începând cu versiunea 7, update 6 (7u6), ceea


ce implică faptul că aplicațiile JavaFX vor putea fi dezvoltate și rulate de către orice
client cu această versiune de Java [20].
 inițial JavaFX a fost un limbaj de scripting, dar acum Oracle pune la dispoziție un API
pentru dezvoltarea aplicațiilor direct în Java, pentru un mai bun management și
reutilizare a codului [20].
 un nou motor (engine) grafic, numit Prism, care face uz de accelerarea hardware
oferită de GPU-urile moderne, precum și un nou manager de ferestre (Window
Toolkit) numit Glass [20].
 un nou limbaj bazat pe XML, numit FXML, folosit pentru descrierea interfețelor
grafice, astfel încât să nu fie nevoie de recompilarea codului la fiecare modificare [20].
 un nou engine multimedia, bazat pe GStreamer, care permite redarea de conținut
multimedia.
 componentă care poate afișa conținut web și care poate fi integrată în orice interfață
grafică JavaFX [20].
 serie de componente noi de interfață, precum grafice, tabele, meniuri și panouri [20].
 un sistem de a împacheta aplicațiile astfel încât acestea să fie livrate cu toate
bibliotecile necesare execuției [20].
 portabilitate pe Linux, Windows și Mac OS X;

JavaFX se bazează pe o ierarhie de clase care implementează diferite componente și


containere ce reprezintă elementele grafice. In JavaFX, clasa care descrie fereastra principală
a unei aplicații este javafx.stage.Stage. O aplicație JavaFX (spre deosebire de o aplicație Java
obișnuită, care pornește cu metoda main()) trebuie să extindă clasa
javafx.application.Application. Aceasta este o clasă abstractă, deci utilizatorul este obligat să
definească metoda public void start(Stage _primaryStage), care este metoda de start a
aplicației, analog metodei main().

5.2. Piesa

O piesă de pe tablă este reprezentată ca un obiect de dimensiune "size x size" împărțit


după cele două diagonale în 4. Fiecare parte a acestui obiect este colorată cu o culoare din
cele n disponibile. Acest obiect este reprezentat printr-o clasa "Piece". Cu ajutorul acestei
clase se generează un "canvas" care va fi ulterior folosit pentru a putea afișa piesa. Clasa
"Piece" permite totodată rotirea unei piese cu animație, la stânga, sau la dreapta, dar și rotirea
acesteia direct pe baza variabilei "rotation". Fiecare piesă are setat un id de forma "Piece-
<numărul piesei>", iar atunci când se generează "canvas-ul" se folosește același id, pentru a se
putea identifica ulterior piesele pe tablă, pentru diverse operații care vor fi explicate ulterior.
Dimensiunile unei piese se stabilesc în funcție de dimensiunile tablei, cu cât tabla este mai
mare, cu atât dimensiunile unei piese vor fi mai mici.

public class Piece{


private PieceColor pieceImage;
private Canvas canvas;
private int rotation = 0;
public Piece(double width, double height, PieceColor pc,String id) {}
...
public void RotatePieceRight(){ }
public void RotatePieceLeft(){}
...
public void rotateCanvas(Canvas canvas){}
public String getId(){}
public int getRotation(){}
public void setRotation(int rotation){}
public Canvas getCanvas(){}
}
Tabela 31: Metodele clasei Piece

5.3. Colorarea pieselor

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

Figura 24: Piesa

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.

În prima etapă a algoritmului se colorează laturile pieselor, utilizând metoda "fillPolygon()".


O astfel de metodă pentru colorarea laturii de sus fiind prezentată Tabela 33 de mai jos.

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

Pentru celelalte laturi se folosește același algoritm cu următoarele modificări în metoda


"fillPolygon", pentru latura stângă {0, 𝑥𝑐𝑒𝑛𝑡𝑟𝑢 , 0} respectiv {0, 𝑦𝑐𝑒𝑛𝑡𝑟𝑢 , 𝑦𝑚𝑎𝑥 } , pentru latura
dreaptă {𝑥𝑚𝑎𝑥 , 𝑥𝑐𝑒𝑛𝑡𝑟𝑢 , 𝑥𝑚𝑎𝑥 } respectiv {0, 𝑦𝑐𝑒𝑛𝑡𝑟𝑢 , 𝑦𝑚𝑎𝑥 }, iar pentru latura de jos
{𝑥𝑐𝑒𝑛𝑡𝑟𝑢 , 0, 𝑥𝑚𝑎𝑥 } respectiv {𝑦𝑐𝑒𝑛𝑡𝑟𝑢 , 𝑦𝑚𝑎𝑥 , 𝑦𝑚𝑎𝑥 }.

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 𝑥𝑚𝑎𝑥 .

După ce au fost alese punctele, utilizând metoda "fillPolygon" se va desena poligonul de la


dimensiunile stabilite în funcție de latura piesei pe care se află. Pentru latura din stânga se va
alege x0 = 0, y0 = ycentru , iar pentru xmax și ymax vor fi utilizate valori convenabile până la
xmax=xcentru. Bucata de cod din Tabela 35 de mai jos, exemplifică modul în care este creat
acest poligon utilizând culoarea "cForm" aleasă inițial.

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.

public class PolygonDraw {


private static PolygonDraw instance;
private PolygonDraw (){}
public static FunctionDrawSingleton getInstance(){
if(instance == null) instance = new FunctionDrawSingleton();
return instance;
}
public void init(double width,double height){
this.width = width;
this.height = height;
this.createf1();
...}
private void createF1(){...}
private void createF2(){...}}
Tabela 36:Metodele clasei Sigleton PolygonDraw

Au fost definite patru poligoane, fiecare dintre ele fiind prezentate în cele ce urmează:

Primul poligon este reprezentat de 𝑥𝑖 = 5 ∗ cos 𝑡 3 , respectiv 𝑦𝑖 = 5 ∗ sin 𝑡 3 cu 𝑡 ∈


[0,2𝜋]. Pentru forma din interior a fost utilizată aceeași funcție doar că a fost scalată pe
dimensiuni mai mici.

Graficul 1

Al doilea poligon este reprezentat de 𝑥𝑖 = (1 + 𝑡) ∗ cos 𝑡 + 𝑎 ∗ cos((1 + 𝑟) ∗ 𝑡)


respectiv 𝑦𝑖 = (1 + 𝑡) ∗ sin 𝑡 + 𝑎 ∗ sin((1 + 𝑟) ∗ 𝑡) cu 𝑡 ∈ [0,2𝜋], 𝑟 = 6, 𝑎 = 1,5. Forma
din interior a fost realizată utilizând același procedeu ca mai sus.

Graficul 2

Al treilea poligon este reprezentat de următoarele coordonate 𝑥𝑖 = (1 + 𝑡) ∗ cos 𝑡 +


𝑎 ∗ cos(−(1 + 𝑟) ∗ 𝑡) respectiv 𝑦𝑖 = (1 + 𝑡) ∗ sin 𝑡 + 𝑎 ∗ sin(−(1 + 𝑟) ∗ 𝑡) cu 𝑡 ∈
[0,2𝜋], 𝑟 = 5, 𝑎 = −1,1. Analog ca mai sus pentru forma din interior.
Graficul 3

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.

public double scaleValue(double value,double oMin, double


oMax,double newMin, double newMax){
return = ((newMax - newMin) * (value - oMin))/(oMax
- oMin) + newMin;
}
Tabela 37: Funcție folosită pentru scalarea unei valori dintr-un interval în altul
Figura 25: Piese create utilizând algoritmii descriși

5.4. Rotire simplă a unei piese

Î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:

ScaleTransition st = new ScaleTransition(Duration.millis(500),this.canvas);


st.setByX(0.2f);
st.setByY(0.2f);
RotateTransition rt = new RotateTransition(Duration.millis(1000),
this.canvas);

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°.

5.5. Tabla sursă şi tabla destinaţie

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.

public class Table {


public static Table getInstance();
public void init();
public void loadFromFile(String path);
public GridPane getTargetGrid();
public GridPane getPieceGrid();
public boolean isSelected();
public void rotatePieceSelectedToRight();
public void rotatePieceSelectedToLeft();
public int getScore();
public void start();
public void pause();
public void resume();
}
Tabela 39: Metodele clasei Singleton Table

În continuare sunt descrise acțiunile pe care utilizatorul le poate face cu obiectele din acest
grid.

Utilizatorul mută o piesă din tabelul sursă în tabelul destinație

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.

double offsetX = event.getSceneX() - orSceneX;


double offsetY = event.getSceneY() - orSceneY;
double newTranslateX = orTranX + offsetX;
double newTranslateY = orTranY + offsetY;
el.setTranslateX(newTranslateX);
el.setTranslateY(newTranslateY);
Tabela 41: Setarea pozițiile în scenă la momentul mutării mouse-lui

Figura 26: Mutarea unei piese

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.

Figura 28: Exemple de mutări incorecte

Utilizatorul mută o piesă din tabelul destinație în tabelul destinație

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ă.

Figura 29: Exemplu de revenire a unei piese in poziția inițiala


Dacă mutarea este corectă, se disting două cazuri : cazul în care locul în care a fost lăsată
piesa este liber, moment în care doar sunt actualizate listele, iar piesa este scoasă din locul
inițial și adăugată în locul destinație, și cazul în care locul în care este lăsată piesa este ocupat,
iar atunci cele două piese sunt interschimbate.

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

Figura 31: Cazul in care cele doua piese sunt interschimbate

Utilizatorul selectează o piesă.

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ă

Utilizatorul rotește o piesă

Î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ă.

stage.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler<KeyEvent>() {


public void handle(KeyEvent event) {
if(event.isConsumed() == false && event.getCode() == KeyCode.LEFT &&
selected == true){
Canvas c = (Canvas) selectedElement.getChildren().get(0);
TablePiece.getInstance().getPieceById(c.getId().RotatePieceLeft();}

} else if (event.isConsumed() == false && event.getCode() ==


KeyCode.RIGHT&& selected == true) {
Canvas c = (Canvas) selectedElement.getChildren().get(0);

TablePiece.getInstance().getPieceById(c.getId()).RotatePieceRight();}
}
event.consume();}});
Tabela 43: Algoritmul permite rotirea unei piese din tastatura

Utilizatorul aduce o piesă în poziția inițială

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ă.

Utilizatorul resetează piesele în poziția inițială

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ă.

Figura 33: Resetarea pieselor de pe tabla destinație pe tabla sursa

5.6. Încărcarea pieselor

Piesele vor fi inițial scrisă într-un fișier având forma prezentată în Figura 34.

Figura 34:Exemplu de fișier în care se găsesc culorile fiecărei piese

La începutul inițializării se încarcă piesele în memorie. Fișierul dat ca parametru se citește


linie cu linie, prima linie va fi ignorata, iar din următoarele linii sunt citite culorile pentru
variabilele sus, jos, stânga, dreapta ale piesei respective. Cu aceste variabile citite se va crea
fiecare piesă având un id unic "Piece-"+numarul_piesei și va fi salvată într-o listă. După
această etapă piesele vor putea fi așezate pe tabla sursă. În secțiunea de cod aflata in Tabela
44 este prezentat modul în care se realizează citirea și crearea celor n piese.

public void loadFromFile(String path){


scanner.nextLine();
while(scanner.hasNextLine()){
int up = scanner.nextInt();
... }
this.pieceWidth = this.width / this.size-2;
this.pieceHeight = this.height / this.size-2;
...
for(int i=0;i<this.size*this.size;i++){
PieceColor pc = new PieceColor(up,down,left,right);
Piece piece = new
Piece(this.pieceWidth,this.pieceHeight,pc,"Piece-"+i+"")
Canvas canvas = piece.CreatePiece();
canvas.setId("Piece-"+i+"");}}
Tabela 44: Funcția permite încărcarea datelor despre piese dintr-un fișier

5.7. Rezolvare prin Algoritmi Genetici

Pe lângă varianta de rezolvare de către utilizator este disponibilă și posibilitatea de a


încerca rezolvarea puzzle-lui cu ajutorul algoritmilor genetici. Aceasta variantă poate conduce
sau nu la soluția finală. Algoritmul utilizat este cel prezentat în capitolul precedent. Acesta se
va putea porni după modul descris în continuare. Soluțiile parțiale sau finale se vor afișa pe
tabelul destinație.

5.8. Pornirea algoritmului

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.

Înainte de a seta workerul se va înregistra în cadrul algoritmului în handler pentru a putea


afișa soluțiile parțiale. Aceasta se va putea realiza definind o clasa PrintHandler care
implementează interfața EventPrint.
public class PrintHandler implements EventPrint{
@Override
public void Catch(final Solution print) {
Platform.runLater(new Runnable() {
@Override public void run() {
Table.getInstance().printSolution(print);
} }); }}
Tabela 46: Implementarea patternului Observer

Următoarea secțiune de cod din cadrul Tabelei 47 prezintă modul în care se poate înregistra
un eveniment.

PrintHandler handler = new PrintHandler();


PopSingleton.getInstance().registerEvents(handler);
Tabela 47

După această etapă se definește un worker care va fi setat în cadrul solverului OmniCS.

Solver solver = new Solver(problemEternityII,false);


Worker worker = (new
CreateWorker()).create(problemEternityII,"Eternity2",et.getFilename(),
solver,60,200,0.25);
solver.addWorker(worker);
Tabela 48

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.

public class SolverThread extends Thread{


private Solver solver;
private Worker worker;
public void setSolver(Solver solver,Worker worker){}

@Override
public void run() {
solver.findSolution();
solver.printStatistics();
}
}
Tabela 49

5.9. Afișarea soluțiilor parțiale

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.

5.11. Repornirea algoritmului

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.

5.12. Oprirea algoritmului

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.

5.13. Terminarea algoritmului

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

În acest capitol sunt prezentate rezultatele obținute în timpul rulării algoritmului


genetic pentru diferite dimensiuni ale instanței problemei Eternity 2. Majoritatea testelor au
fost efectuate pe un procesor de 2.4 GHz cu 8 Gb memorie ram, și un sistem de operare
Windows 7 de 64 biti.

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ă.

În Tabela 51 sunt trecuți parametrii algoritmului genetic(dimensiunea populatiei,


diferiți parametri utilizați), cei care au fost folosiți în evaluările ulterioare. Schema de selecție
folosită a fost selecția prin metoda lui Gauss, paramerul α din urmatorul tabel reprezintă panta
cu care descrește probabilitatea ca un individ să fie selectat.

Parametri algoritm genetic


Dimensiunea populației 300 cromozomi
Numărul maxim de generații 7000 de generații
Timpul maxim de lucru 7000 mS
Probabilitatea de mutație(pM) 0.5
Probabilitatea de încrucișare(pC) 0.9
Probabilitatea de selecție(α) 0.25
Elitism 1
Tabela 51

În următoarele grafice este evidențiată evoluția funcției fitness de-a lungul


generațiilor, pentru instanțe ale puzzle-ului de diferite dimensiuni.

Graficul 5:Evoluția funcției fitness pentru un puzzle de dimensiune 5x5


Graficul 6: Evoluția funcției fitness pentru un puzzle de dimensiune 6x6

Graficul 7: Evoluția funcției fitness pentru un puzzle de dimensiune 7x7

Graficul 8: Evoluția funcției fitness pentru un puzzle de dimensiune 8x8


Graficul 9: Evoluția funcției fitness pentru un puzzle de dimensiune 9x9

Graficul 10: Evoluția funcției fitness pentru un puzzle de dimensiune 10x10

Dimensiunea maximă pentru care algoritmul ajunge la o solutie optimă este


10x10.Pentru fiecare din cele 5 dimensiuni ale puzzle-ului (5x5, 6x6, 7x7, 8x8, 9x9, 10x10)
au fost efectuate câte zece rulări ale algoritmului astfel calculându-se timpul mediu de
funcționare. În Tabela 52 de mai jos sunt prezentate rezultatele obtinute(timpul mediu de
rulare, numarul de generatii mediu) pentru puzzle-luri de diferite dimensiuni.

Dimensiunea Tablei Timpul mediu Nr. de generații mediu


5x5 4s 17,300
6x6 24s 35,200
7x7 68s 135,560
8x8 320s 712,140
9x9 2169s 2568,800
10x10 6251s 5825,700
Tabela 52: Rezultatele obținute în urma rulării algoritmului genetic

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.

Graficul 11:Timpul de lucru al algoritmului genetic în funcție de dimensiunea problemei


Concluzii

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.

Pentru problemele de satisfacere a constrângerilor, utilizarea algoritmilor genetici este


utilă, în cazul în care constrângerile sunt binare, iar numărul acestora este mare (aproape de
numărul de variabile implicate în constrângeri). De exemplu pentru o problemă AllDiff în
care există o singură constrângere pentru toate valorile, rezultatul funcției fitness ar avea doar
două valori, 0 sau 1, iar șansele ca soluția oferită de algoritmul genetic sa fie optimă sunt
foarte mici.

Atunci când o problemă este modelată ca o problemă de satisfacere a


constrângerilor(CSP) în scopul utilizării algoritmilor genetici pentru obținerea soluțiilor
acestora trebuie avute în vedere rezultatele din concluzia de mai sus. În continuare este
prezentat un exemplu de modelare a problemei AllDiff astfel încât să fie util în cadrul
rezolvării AG:

𝑉 = {𝑉1 , 𝑉2 , … , 𝑉𝑛 }

𝐷𝑖 = {1, … , 𝑛}

𝐶𝑣𝑖 𝑣𝑗 = {𝑉𝑖 ≠ 𝑉𝑗 , ∀𝑖 ≠ 𝑗, 𝑖, 𝑗 ∈ [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 posibilă soluție pentru problema Eternity 2 privind îmbunătățirea rezultatelor


obținute de algoritmul genetic pentru dimensiuni mai mari de 10x10 ar putea fi următoarea:
adăugarea de noi operatori de mutație și încrucișare, de exemplu încrucișarea să interschimbe
regiuni mai mari de 2x2, mutația să folosească regiuni mai mari de 2x2.

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".

[5] Alice E. Smith and David W. Coit, “Constraint-Handling Techniques - Penalty


Functions”, 1996, Handbook of Evolutionary Computation, Institute of Physics Publishing
and Oxford University Press, Bristol, U.K., 1997, capitolul C5.2.

[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.

[9] Yasuhiko Takenaga, Toby Walshb, "TETRAVEX is NP-complete", Information


Processing Letters 99, 2006, 171–174.

[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.

[11] Springer Berlin Heidelberg, "Approximability of Edge Matching Puzzles", 36th


Conference on Current Trends in Theory and Practice of Computer Science, Czech Republic,
2010.

[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.

[14] Stuart Cam., "Eternity II Solver", Codebrain, 2010.

[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.

[17] Tomy, "Eternity II" (official site) (November 2008), http://www.eternityii.com.

[18] Dechter, R., "Constraint Processing". Morgan Kaufmann, San Francisco (2003).

[19] Wikipedia, "Eternity II Puzzle", 12 decembrie 2009, Wikipedia site:


http://en.wikipedia.org/wiki/Eternity_II_puzzle.

[20] Wikipedia, "JavaFX", 18 martie 2014, Wikipedia site:


http://en.wikipedia.org/wiki/JavaFX.

[21] Panos Toulis, "The Eternity Puzzle", 11 decembrie 2009.

[22] John H. Holland, "Genetic Algorithms and Adaptation", 1984, Springer US, paginile
314-333.

[23] Antonios Antoniadis,Andrzej Lingas, "Approximability of Edge Matching Puzzles",


2010, SOFSEM 2010: Theory and Practice of Computer Science, paginile 153-164

S-ar putea să vă placă și