Differences

This shows you the differences between two versions of the page.

Link to this comparison view

pp:25:teme:haskell-sat [2025/05/01 10:48]
mihnea.muraru [Învățarea de noi clauze]
pp:25:teme:haskell-sat [2025/05/08 23:34] (current)
mihnea.muraru [Haskell: SAT Solving]
Line 2: Line 2:
  
   * Data publicării:​ 09.04.2025   * Data publicării:​ 09.04.2025
-  * Data ultimei modificări: ​30.04.2025+  * Data ultimei modificări: ​08.05.2025
   * Deadline hard: ziua laboratorului 10   * Deadline hard: ziua laboratorului 10
   * [[https://​curs.upb.ro/​2024/​mod/​forum/​view.php?​id=152382|Forum]]   * [[https://​curs.upb.ro/​2024/​mod/​forum/​view.php?​id=152382|Forum]]
Line 131: Line 131:
 O altă situație interesantă vizează **literalii puri**. Din moment ce complementul unui literal nu apare în formulă, este întotdeauna avantajos să asumăm acel literal, fără teama inducerii vreunui conflict. La fel ca la clauzele unitare, eliminarea unui literal pur poate genera **noi literali puri**, deci prelucrarea trebuie **repetată**. De exemplu, în formula ''​(x1 ∨ x2) ∧ (¬x2 ∨ x3) ∧ (¬x2 ∨ ¬x3)'',​ nu există clauze unitare, dar există un singur literal pur, ''​x1''​. Prin eliminarea lui, se obține formula ''​(¬x2 ∨ x3) ∧ (¬x2 ∨ ¬x3)'',​ în care apare literalul pur ''​¬x2'',​ care nu era pur în formula originală. Eliminându-l și pe acesta, se obține formula vidă. O altă situație interesantă vizează **literalii puri**. Din moment ce complementul unui literal nu apare în formulă, este întotdeauna avantajos să asumăm acel literal, fără teama inducerii vreunui conflict. La fel ca la clauzele unitare, eliminarea unui literal pur poate genera **noi literali puri**, deci prelucrarea trebuie **repetată**. De exemplu, în formula ''​(x1 ∨ x2) ∧ (¬x2 ∨ x3) ∧ (¬x2 ∨ ¬x3)'',​ nu există clauze unitare, dar există un singur literal pur, ''​x1''​. Prin eliminarea lui, se obține formula ''​(¬x2 ∨ x3) ∧ (¬x2 ∨ ¬x3)'',​ în care apare literalul pur ''​¬x2'',​ care nu era pur în formula originală. Eliminându-l și pe acesta, se obține formula vidă.
  
-De remarcat că cele două prelucrări de mai sus pot **interacționa**:​ satisfacerea unei clauze unitare poate introduce nu numai alte clauze unitare, ci și literali puri, iar eliminarea unui literal pur poate introduce nu numai alți literali puri, ci și clauze unitare. În cazul în care sunt disponibile ambele opțiuni, se preferă **satisfacerea clauzelor unitare mai întâi**, întrucât ele pot conduce la **conflicte**,​ și este de dorit evidențierea **cât mai timpurie** a acestora, pentru a scuti alt efort de calcul care oricum nu ar împiedica generarea unui conflict. Numai dacă formula nu conține **nici clauze unitare**, și **nici literali puri**, are rost să recurgem la **asumpții oarecare**.+De remarcat că cele două prelucrări de mai sus pot **interacționa**:​ satisfacerea unei clauze unitare poate introduce nu numai alte clauze unitare, ci și literali puri. În cazul în care sunt disponibile ambele opțiuni, se preferă **satisfacerea clauzelor unitare mai întâi**, întrucât ele pot conduce la **conflicte**,​ și este de dorit evidențierea **cât mai timpurie** a acestora, pentru a scuti alt efort de calcul care oricum nu ar împiedica generarea unui conflict. Numai dacă formula nu conține **nici clauze unitare**, și **nici literali puri**, are rost să recurgem la **asumpții oarecare**.
  
 În exemplul final, demonstrăm toate cele trei tipuri de prelucrări pe formula ''​(¬x1 ∨ x4) ∧ (x1 ∨ x2 ∨ ¬x3) ∧ (¬x2 ∨ x3 ∨ ¬x4)'':​ În exemplul final, demonstrăm toate cele trei tipuri de prelucrări pe formula ''​(¬x1 ∨ x4) ∧ (x1 ∨ x2 ∨ ¬x3) ∧ (¬x2 ∨ x3 ∨ ¬x4)'':​
Line 182: Line 182:
 Ce ar trebui făcut în acest caz? Intuitiv, ar trebui revenit prin **//​backtracking//​** la o asumpție anterioară și explorat opusul ei. Dar acest lucru ridică o altă întrebare: la ce punct decizional ar trebui să ne întoarcem? Evident, **clauzele unitare impun alegerea**, și nu ar avea sens să încercăm opusul; de exemplu, revenirea la asumpția ''​¬x2''​ (pasul 4 de mai sus), dictată de clauza unitară ''​(¬x2)'',​ și explorarea variantei ''​x2''​ ar fi inutile. Prin urmare, ar trebui să revenim la cel mai recent punct decizional în care există o **alternativă viabilă**; de exemplu, am putea reveni la asumpția ''​¬x5''​ (pasul 3), arbitrară, și explora asumpția ''​x5''​. Din păcate, și această cale ar conduce la conflict, și ar trebui revenit un nivel și mai sus, la asumpția ''​¬x6''​ (pasul 2) etc. Ce ar trebui făcut în acest caz? Intuitiv, ar trebui revenit prin **//​backtracking//​** la o asumpție anterioară și explorat opusul ei. Dar acest lucru ridică o altă întrebare: la ce punct decizional ar trebui să ne întoarcem? Evident, **clauzele unitare impun alegerea**, și nu ar avea sens să încercăm opusul; de exemplu, revenirea la asumpția ''​¬x2''​ (pasul 4 de mai sus), dictată de clauza unitară ''​(¬x2)'',​ și explorarea variantei ''​x2''​ ar fi inutile. Prin urmare, ar trebui să revenim la cel mai recent punct decizional în care există o **alternativă viabilă**; de exemplu, am putea reveni la asumpția ''​¬x5''​ (pasul 3), arbitrară, și explora asumpția ''​x5''​. Din păcate, și această cale ar conduce la conflict, și ar trebui revenit un nivel și mai sus, la asumpția ''​¬x6''​ (pasul 2) etc.
  
-Se ridică următoarea întrebare interesantă:​ am putea oare reveni într-un **punct mai precis din trecut**, astfel încât **să evităm** încercarea măcar a anumitor ​alternative care în final ar conduce **tot la conflict**? În exemplul de mai sus, atât secvența inițială ''​¬x7 - ¬x6 - ¬x5'',​ cât și secvența alternativă ''​¬x7 - ¬x6 - x5''​ au condus la conflicte. Puteam să o evităm cu totul pe a doua?+Se ridică următoarea întrebare interesantă:​ am putea oare reveni într-un **punct mai precis din trecut**, astfel încât **să evităm** încercarea măcar a câtorva ​alternative care în final ar conduce **tot la conflict**? În exemplul de mai sus, atât secvența inițială ''​¬x7 - ¬x6 - ¬x5'',​ cât și secvența alternativă ''​¬x7 - ¬x6 - x5''​ au condus la conflicte. Puteam să o evităm cu totul pe a doua?
  
 La baza răspunsului stă observația că un conflict poate fi indus de fapt **timpuriu**,​ printr-o anumită **combinație de asumpții incompatibile** realizate în trecut, dar manifestat **târziu**. În exemplul de mai sus, combinația de asumpții incompatibile este ''​{¬x7,​ ¬x5}'',​ care conduce la clauza unitară ''​(¬x2)'',​ provenită din clauza originală ''​(¬x2 ∨ x5 ∨ x7)''​. Eliminarea acesteia conduce pe ambele căi de mai sus la conflicte. Prin urmare, ar trebui revenit într-un **punct cât mai distant din trecut** în care incompatibilitatea să fie **înlăturată**,​ astfel încât toate asumpțiile făcute ulterior asupra altor variabile să se armonizeze cu modificarea realizată. Acest lucru nu previne complet apariția altor conflicte pe măsură ce se realizează noi asumpții, dar **reduce spațiul de căutare**. La baza răspunsului stă observația că un conflict poate fi indus de fapt **timpuriu**,​ printr-o anumită **combinație de asumpții incompatibile** realizate în trecut, dar manifestat **târziu**. În exemplul de mai sus, combinația de asumpții incompatibile este ''​{¬x7,​ ¬x5}'',​ care conduce la clauza unitară ''​(¬x2)'',​ provenită din clauza originală ''​(¬x2 ∨ x5 ∨ x7)''​. Eliminarea acesteia conduce pe ambele căi de mai sus la conflicte. Prin urmare, ar trebui revenit într-un **punct cât mai distant din trecut** în care incompatibilitatea să fie **înlăturată**,​ astfel încât toate asumpțiile făcute ulterior asupra altor variabile să se armonizeze cu modificarea realizată. Acest lucru nu previne complet apariția altor conflicte pe măsură ce se realizează noi asumpții, dar **reduce spațiul de căutare**.
Line 206: Line 206:
   - În momentul obținerii unei **clauze vide**, se determină **clauza originală** din care aceasta a provenit, care servește drept **clauză curentă**.   - În momentul obținerii unei **clauze vide**, se determină **clauza originală** din care aceasta a provenit, care servește drept **clauză curentă**.
   - Se parcurge istoricul de asumpții realizate în **sens anticronologic** (prezent-trecut) și:   - Se parcurge istoricul de asumpții realizate în **sens anticronologic** (prezent-trecut) și:
-    - Dacă acțiunea curentă este de eliminare a unei **clauze unitare** (''​Unit''​),​ **rezolvăm** (dacă este posibil, ​vedeți ​definiția rezoluției de mai sus) clauza stocată în acțiune cu clauza curentă, în raport cu literalul stocat de asemenea în acțiune, rezultând o nouă clauză curentă.+    - Dacă acțiunea curentă este de eliminare a unei **clauze unitare** (''​Unit''​),​ **rezolvăm** (dacă este posibil, ​conform ​definiției de mai sus a rezoluției) clauza stocată în acțiune cu clauza curentă, în raport cu literalul stocat de asemenea în acțiune, rezultând o nouă clauză curentă.
     - Dacă acțiunea curentă este de altă natură, clauză curentă rămâne neschimbată.     - Dacă acțiunea curentă este de altă natură, clauză curentă rămâne neschimbată.
   - Varianta finală a clauzei curente rezumă **motivul conflictului**.   - Varianta finală a clauzei curente rezumă **motivul conflictului**.
Line 218: Line 218:
   * 3, 2, 1. Acțiunile rămase nu sunt de eliminare a unei clauze unitare, și prin urmare clauza curentă rămâne ''​(x5 ∨ x7)''​.   * 3, 2, 1. Acțiunile rămase nu sunt de eliminare a unei clauze unitare, și prin urmare clauza curentă rămâne ''​(x5 ∨ x7)''​.
  
-Pentru a înțelege semnificația acestei clauze, ''​(x5 ∨ x7)'',​ să ne amintim **combinația de asumpții incompatibile** depistată mai sus, ''​{¬x7,​ ¬x5}'',​ conform căreia ambele variabile, ''​x7''​ și ''​x5'',​ au fost asumate false. Pentru a depăși incompatibilitatea,​ **cel puțin una dintre variabile** trebuie asumată adevărată,​ adică exact ce **codifică clauza** ''​(x5 ∨ x7)''​. ​Pornind de la proprietatea ​rezolventului că este întotdeauna adevărat, dacă clauzele pe baza căruia a fost obținut sunt adevărate, conchidem că, dacă vrem ca clauza vidată și clauzele unitare eliminate ​pe parcurs ​să fie **simultan adevărate** (într-o anumită interpretare),​ atunci **și clauza** ''​(x5 ∨ x7)''​ trebuie să fie adevărată (în acea interpretare). ​''​(x5 ∨ x7)''​ poartă numele de **//clauză învățată//** (//learned clause//)întrucât ​nu face parte din formula originală.+Pentru a înțelege semnificația acestei clauze, ''​(x5 ∨ x7)'',​ să ne amintim **combinația de asumpții incompatibile** depistată mai sus, ''​{¬x7,​ ¬x5}'',​ conform căreia ambele variabile, ''​x7''​ și ''​x5'',​ au fost asumate false. Pentru a depăși incompatibilitatea,​ **cel puțin una dintre variabile** trebuie asumată adevărată,​ adică exact ce **codifică clauza** ''​(x5 ∨ x7)''​. ​Proprietatea ​rezolventului ​afirmă ​că acesta ​este întotdeauna adevărat, dacă clauzele pe baza căruia a fost obținut sunt adevărate. Prin urmare, dacă vrem ca clauza vidată ​''​(x1 ∨ x2)'' ​și clauzele ​devenite ​unitare ​și eliminate ​''​(¬x1 ∨ x2)''​ și ''​(¬x2 ∨ x5 ∨ x7)'' ​să fie **simultan adevărate** (într-o anumită interpretare),​ atunci **și clauza** ''​(x5 ∨ x7)''​ trebuie să fie adevărată (în acea interpretare). ​Observați cum într-adevăr clauza referă doar variabile asupra cărora s-au realizat **asumpții arbitrare**, ca ''​x5''​ și ''​x7'', ​nu și variabile pentru care asumpțiile au fost impuse, ca ''​x2''​.
  
-Prin urmarepentru ​a ne asigura că **noua clauză**, ''​(x5 ∨ x7)'',​ nu va mai fi niciodată vidată de asumpțiile realizate, o putem **adăuga la formula originală**,​ și ne putem întoarce în trecut la **cel mai distant punct** în care această clauză **devine unitară** (funcția ''​backtrackToUnitClause''​ din etapa 2), astfel încât următoarea acțiune să o **satisfacă imediat**. Revenirea se poate face cu un număr arbitrar de pași în trecut, motiv pentru care poartă numele de **//​backtracking necronologic//​**. Revenirea la cel mai distant punct, și nu la cel mai recent, scade probabilitatea de eșec al căii curente, ​datorate ​altor asumpții realizate pe anterioara cale eșuată. Astfel, răspundem și la **întrebarea (2)**. Remarcăm că, din moment ce rezolventul derivă logic din anumite clauze ale formulei originale, formula rezultată prin adăugarea clauzei învățate la formula originală este **echivalentă** cu formula originală.+''​(x5 ∨ x7)''​ poartă numele de **//clauză învățată//​** (//learned clause//)întrucât nu face parte din formula originală. Dacă **clauza învățată însăși** este **vidă**, înseamnă că este **imposibil** ca clauzele din care a fost obținută să fie adevărate simultan, și deci formula originală este **nesatisfiabilă**. 
 + 
 +Pentru ​a ne asigura că **noua clauză**, ''​(x5 ∨ x7)'',​ nu va mai fi niciodată vidată de asumpțiile realizate, o putem **adăuga la formula originală**,​ și ne putem întoarce în trecut la **cel mai distant punct** în care această clauză **devine unitară** (funcția ''​backtrackToUnitClause''​ din etapa 2), astfel încât următoarea acțiune să o **satisfacă imediat**. Revenirea se poate face cu un număr arbitrar de pași în trecut, motiv pentru care poartă numele de **//​backtracking necronologic//​**. Revenirea la cel mai distant punct, și nu la cel mai recent, scade probabilitatea de eșec al căii curente, ​datorat ​altor asumpții realizate pe anterioara cale eșuată. Astfel, răspundem și la **întrebarea (2)**. Remarcăm că, din moment ce rezolventul derivă logic din anumite clauze ale formulei originale, formula rezultată prin adăugarea clauzei învățate la formula originală este **echivalentă** cu formula originală.
  
 În exemplul de mai sus, se adaugă clauza învățată ''​(x5 ∨ x7)''​ la formula originală, obținându-se formula ''​(¬x7 ∨ x1) ∧ (¬x6 ∨ ¬x2 ∨ x1) ∧ (¬x5 ∨ x1) ∧ (¬x4 ∨ x3) ∧ (¬x3 ∨ x4) ∧ (¬x2 ∨ ¬x1 ∨ x6) ∧ (¬x2 ∨ x5 ∨ x7) ∧ (¬x1 ∨ x2) ∧ (x1 ∨ x2) ∧ (x5 ∨ x7)'':​ În exemplul de mai sus, se adaugă clauza învățată ''​(x5 ∨ x7)''​ la formula originală, obținându-se formula ''​(¬x7 ∨ x1) ∧ (¬x6 ∨ ¬x2 ∨ x1) ∧ (¬x5 ∨ x1) ∧ (¬x4 ∨ x3) ∧ (¬x3 ∨ x4) ∧ (¬x2 ∨ ¬x1 ∨ x6) ∧ (¬x2 ∨ x5 ∨ x7) ∧ (¬x1 ∨ x2) ∧ (x1 ∨ x2) ∧ (x5 ∨ x7)'':​
Line 248: Line 250:
 ==== Aplicație la problema 3-colorare ==== ==== Aplicație la problema 3-colorare ====
  
-Problema **//​3-colorare//​** a unui **graf neorientat** urmărește asocierea ​uneia dintre trei culori (''​Red'',​ ''​Green'',​ ''​Blue''​) fiecărui nod din graf, astfel încât oricare două **noduri adiacente** să fie **colorate diferit**. Problema poate fi rezolvată prin **reducere la SAT**, parcurgând etapele:+Problema **//​3-colorare//​** a unui **graf neorientat** urmărește asocierea ​unei culori ​din trei (''​Red'',​ ''​Green'',​ ''​Blue''​) fiecărui nod din graf, astfel încât oricare două **noduri adiacente** să fie **colorate diferit**. Problema poate fi rezolvată prin **reducere la SAT**, parcurgând etapele:
  
   - **Codificarea** instanței //​3-colorare//​ într-o formulă CNF.   - **Codificarea** instanței //​3-colorare//​ într-o formulă CNF.
Line 276: Line 278:
 A treia etapă a temei abordează **rezolvarea** clauzelor, **învățarea** clauzelor, algoritmul de **satisfacere** și aplicația la problema **//​3-colorare//​**. A treia etapă a temei abordează **rezolvarea** clauzelor, **învățarea** clauzelor, algoritmul de **satisfacere** și aplicația la problema **//​3-colorare//​**.
  
-Construcțiile și mecanismele de limbaj pe care le veți exploata în rezolvare, pe lângă cele din etapa 1 și 2, sunt:+Construcțiile și mecanismele de limbaj pe care le veți exploata în rezolvare, pe lângă cele din etapele ​1 și 2, sunt:
  
   * **polimorfismul ad-hoc**   * **polimorfismul ad-hoc**
Line 283: Line 285:
 Modulul de interes din **schelet** este ''​Solver'',​ care conține **operațiile** pe care trebuie să le implementați. Găsiți detaliile despre **funcționalitate** și despre **constrângerile de implementare**,​ precum și **exemple**,​ direct în fișier. Aveți de completat definițiile care încep cu ''​%%*** TODO ***%%''​. Modulul de interes din **schelet** este ''​Solver'',​ care conține **operațiile** pe care trebuie să le implementați. Găsiți detaliile despre **funcționalitate** și despre **constrângerile de implementare**,​ precum și **exemple**,​ direct în fișier. Aveți de completat definițiile care încep cu ''​%%*** TODO ***%%''​.
  
-Pentru **rularea testelor**, încărcați în interpretor modulul ''​TestSolver''​ și evaluați ''​main''​.+Pentru **rularea testelor**, încărcați în interpretor modulul ''​TestSolver''​ și evaluați ''​main''​. Ultimul test (''​stress''​) utilizează grafuri mai mari și execuția poate dura câteva secunde.
  
 Este suficient ca arhiva pentru **vmchecker** să conțină modulele ''​Solver'',​ ''​ExtendedFormula''​ din etapa 2 și ''​Formula''​ din etapa 1. Este suficient ca arhiva pentru **vmchecker** să conțină modulele ''​Solver'',​ ''​ExtendedFormula''​ din etapa 2 și ''​Formula''​ din etapa 1.
Line 300: Line 302:
 ===== Changelog ===== ===== Changelog =====
  
 +  * 08.05 (23:30): Publicat testele etapei 3 și actualizat tipurile ''​Graph'',​ ''​Color''​ și ''​ThreeColoring''​ din ''​Solver.hs''​ cu ''​deriving (Show, Eq)''​ în loc de ''​deriving Show''​.
   * 30.04 (22:30): Publicat etapa 3, momentan fără teste   * 30.04 (22:30): Publicat etapa 3, momentan fără teste
   * 27.04 (09:50): Etapa 2: Actualizat teste ''​backtrackToUnitClause''​   * 27.04 (09:50): Etapa 2: Actualizat teste ''​backtrackToUnitClause''​
pp/25/teme/haskell-sat.1746085698.txt.gz · Last modified: 2025/05/01 10:48 by mihnea.muraru
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0