Algorithm Engineering „Symbolische Suche“ Peter Kissmann
Algorithm Engineering „Symbolische Suche“
Peter Kissmann
Spiele
Einpersonenspiele(n² - 1)-PuzzleSolitär
ZweipersonenspieleTic-Tac-ToeClobberVier Gewinnt
Motivation
Zustandsraumexplosion#erreichbare Zustände:
(n²-1)-Puzzle: (n²)!/2• 15-Puzzle: ≈ 1013
• 24-Puzzle: ≈ 7,8 x 1024
• 35-Puzzle: ≈ 1,9 x 1041
Solitär: 375 110 246 Clobber (4x5): 26 787 440 4 Gewinnt: ≤ 70 728 639 995 483 (≈ 7 x 1013) (Allis, 1988)
(tatsächlich: 4 531 985 219 092 (≈ 4,5 x 1012))
Motivation
Speicher sparen z.B. mittels Binären Entscheidungsdiagrammen (BDDs)
verwalten Zustandsmengensparen unnötige Knoten ein → teils exponentiell viele
Beispiel: vollständiges Lösen von allgemeinen Spielen (General Game Playing)
Überblick
Wiederholung: BDDs BDD-basierte Suche BFS, Dijkstra, A* Anwendung auf allgemeine Spiele („General Game
Playing“) BDDs als perfekte Hash-Funktion
Überblick
Wiederholung: BDDs BDD-basierte Suche BFS, Dijkstra, A* Anwendung auf allgemeine Spiele („General Game
Playing“) BDDs als perfekte Hash-Funktion
BDDs (Binary Decision Diagrams)
Repräsentieren Zustandsmenge gerichteter azyklischer Graph von Wurzel zu 0- oder 1-
Senke Knoten für (binäre) Variablen
Zwei Ausgänge: low und high (auch 0 und 1)
Pfad von Wurzel bis 1-SenkeZustand entsprechender Variablenbelegung in repräsentierter Menge
enthalten
OBDDs (Ordered BDDs)
Feste Variablenordnung πGute Variablenordnung → exponentiell weniger Knoten
(möglicherweise)Finden guter Variablenordnung NP-schwer
Graphisch: Schichten gleicher Variablen
ROBDDs (Reduced OBDDs)
Zwei Vereinfachungsregeln:
ROBDDs eindeutig Im Folgenden nur ROBDDs
x1 x1 x1
x2 x3 x3x2
BDDs für logische Operatoren
x1
x2
0 1
21 xx
x1
x2
0 1
21 xx
x1
0 1
1x
x1
x2 x2
0 1
21 xx
ROBDDs (Beispiele)column-x
row-x
diagonal-x
Überblick
Wiederholung: BDDs BDD-basierte Suche BFS, Dijkstra, A* Anwendung auf allgemeine Spiele („General Game
Playing“) BDDs als perfekte Hash-Funktion
BDD-basierte Suche (Voraussetzungen) S Menge aller Zustände Initialzustand I ∈ S Menge von Zielzuständen G ⊆ S Transitionsrelation T ⊆ S x S
beschreibt Zustandsübergänge durch Vorgänger und Nachfolger
mögliche Ziele:finde kürzesten Pfad von I nach g ∈ Gberechne alle erreichbaren Zustände
2 Variablensätze:x für Vorgängervariablenx‘ für Nachfolgervariablen
in Variablenordnung xi und xi‘ abwechselnd (interleaved)
BDD-basierte Suche
Finden von Nachfolgern (image)Relationales Produkt:
Finden von Vorgängern (pre-image) analog:
zusätzlich: nach jedem (pre-)image:Verschieben der Variablen
xsxxTxsimage ',.
'','. xsxxTxspreimage
BDD-basierte Suche
Partitionierte Berechnung:T = VaTa für alle Aktionen a
∃ und ∨ kommutieren
(entsprechend auch für pre-image)Vorteil: Berechnung monolithischer Transitionsrelation teuer (Zeit und
Speicher)
xsxxTxsimage aa ',.
BDD-basierte Suche Finden der Vorgänger, deren Nachfolger alle in s liegen (strong pre-image):
strong pre-image auf pre-image zurückführbar → Übungsaufgabe '','._ xsxxTxspreimagestrong
BDD-basierte Suche
image pre-image strong pre-image
Überblick
Wiederholung: BDDs BDD-basierte Suche BFS, Dijkstra, A* Anwendung auf allgemeine Spiele („General Game
Playing“) BDDs als perfekte Hash-Funktion
Breitensuche (SBFS)
iterativ images berechnenreach ← Iwiederhole
newBDD ← image(reach) ∧ ⌐reach reach ← reach ∨ newBDD
solange Abbruchkriterium nicht erfüllt
mögliche Abbruchkriterien:newBDD = ⊥ (alle Zustände bestimmt)reach ∧ G ≠ ⊥ (kürzester Weg zum Ziel gefunden)
Mögliche Verbesserung
Jeden Zustand nur einmal expandieren (Duplikatserkennung)
Dazu: Closed-BDDfront ← Iwiederhole
closed ← closed ∨ front front ← image(front) ∧ ⌐closed
solange Abbruchkriterium nicht erfüllt
Bestimmung erreichbarer Zustände mittels SBFS
v: Anzahl Variablen für einen Zustandn: Anzahl BDD-Knoten zur Repräsentation aller
Zuständes: Anzahl aller erreichbarer Zustände
Bestimmung erreichbarer Zustände in „Vier Gewinnt“ (SBFS)
Bestimmung erreichbarer Zustände in „Vier Gewinnt“ (SBFS)
1E+00
1E+01
1E+02
1E+03
1E+04
1E+05
1E+06
1E+07
1E+08
1E+09
1E+10
1E+11
1E+12
1E+13
0 5 10 15 20 25 30 35 40
Knoten (BDD)Zustände (BDD)
Zustände (Allis-Schätzung)
Bidirektionale Breitensuche (SBBFS)
I
G
Schnittgefunden
Bidirektionale Breitensuche (SBBFS)
BFS von Start und Ziel „gleichzeitig“Ende, wenn Suchfronten überschneiden
ffront ← I, bfront ← G wiederhole
• falls vorwärts ffront ← image(ffront)
• sonst bfront ← pre-image(bfront)
solange ffront ∧ bfront = ⊥
Auswahlkriterium etwa Zeit der letzten IterationVerwendung von closed-BDDs möglich
Symbolischer Dijkstra
BFS nur bei uniformen Kosten Gewichtete Transitionsrelation → „Single Source Shortest
Path“→ DijkstraKosten c ∈ {1, …, C}T = VcTc
Symbolischer Dijkstra
open0 ← I, closed ← ⊥, g ← 0
wiederhole falls (openg ∧ G ≠ ⊥) STOPP
openg ← openg ∧ ⌐closed
für c ← 1, …, C
• openg+c ← openg+c ∨ imagec(openg)
closed ← closed ∨ openg
g ← g + 1
Symbolisches A* (BDDA*)
Ähnlich Dijkstra; Expansion nach f-Wert:
Verwendung einer Heuristikz.B. aus Musterdatenbank (pattern database (PDB))Heuristik h darf nicht überschätzen (zulässig)
h = 0 → Dijkstra
vhvgvf
Symbolisches A* (BDDA*)
h
g
Symbolisches A* (BDDA*) open(0,h(I)) ← I, closed(0, …, |h|) ← ⊥, f ← h(I) wiederhole
für g ← 0, …, f h ← f - g falls (h = 0 & open(g, h) ∧ G ≠ ⊥) STOPP open(g, h) ← open(g, h) ∧ ⌐ closed(h) für c ← 1, …, C
• succc ← imagec(open(g, h))
• für hsucc ← 0, …, |h| open(g + c, hsucc) ← open(g + c, hsucc) ∨ (succc ∧ hsucc)
closed(h) ← closed(h) ∨ open(g, h)
f ← f + 1
Überblick
Wiederholung: BDDs BDD-basierte Suche BDD-BFS, BDD-Dijkstra, BDDA* Anwendung auf allgemeine Spiele („General Game
Playing“) BDDs als perfekte Hash-Funktion
Überblick 2 (Lösen allgemeiner Spiele) General Game Playing Einpersonenspiele Zweipersonenspiele
Zweipersonen-NullsummenspieleZweipersonenspiele mit allgemeinen Gewinnen
Überblick 2 (Lösen allgemeiner Spiele) General Game Playing Einpersonenspiele Zweipersonenspiele
Zweipersonen-NullsummenspieleZweipersonenspiele mit allgemeinen Gewinnen
General Game Playing
Beschreibung für Spiele mit folgenden Eigenschaften:endlichdiskretdeterministischvollständige Information
Spiele könnenEin- oder Mehr-Personenspiele seingleichzeitige oder abwechselnde Züge ermöglichen
General Game Playing „Game Description Language“ (GDL) Gegeben:
InitialzustandBestimmung legaler ZügeEffekt eines ZugesTerminierungsbedingungenVerteilung der Gewinne {0, …, 100} darin
Gesucht:Lösung erreichbarer ZuständeBestimmung optimaler Gewinn-Verteilung
General Game Playing
Beispiele:Blocksworld
Original GDL-Datei: .kif
Tic-Tac-Toe Original GDL-Datei: .kif
Mehr Informationen:http://games.stanford.edu (dort entwickelt; leider veraltet)http://www.general-game-playing.dehttp://euklid.inf.tu-dresden.de:8180/ggpserver (aktuelle Spiele etc.)
Überblick 2 (Lösen allgemeiner Spiele) General Game Playing Einpersonenspiele Zweipersonenspiele
Zweipersonen-NullsummenspieleZweipersonenspiele mit allgemeinen Gewinnen
Lösen von Einpersonenspielen
Erst: Erreichbare Zustände finden (BFS) Dann: Rückwärtssuche
Start: Zielzustände mit Gewinn 100 BFS (rückwärts)
Weiter: Zielzustände mit Gewinn 99 BFS (rückwärts) dabei: bereits gelöste Zustände auslassen
Weiter bis Gewinn 0
Lösen von Einpersonenspielen
75 100 90 80
99
75
80100
100
100
100
90
90
80
80
75
Ergebnisse für Solitär
Erreichbar: 375 110 246 Zustände
Überblick 2 (Lösen allgemeiner Spiele) General Game Playing Einpersonenspiele Zweipersonenspiele
Zweipersonen-NullsummenspieleZweipersonenspiele mit allgemeinen Gewinnen
Lösen von Zweipersonen-Nullsummenspielen Mögliche Gewinne: 0, 50, 100 Jeder Spieler versucht, möglichst hohen Gewinn zu
erreichen Lösung liefert Verteilung der Gewinne (bei optimaler
Spielweise)
Lösen von Zweipersonen-Nullsummenspielen BFS für Finden erreichbarer Zustände Zwei Rückwärtssuchen (eine pro Spieler):
Start bei verlorenen Zielzuständen Bestimmung verlorener Vorgänger (2 Schritte)
für alle Züge, die Spieler durchführen kann, kann Gegenspieler Zug zu verlorenem Zustand wählen (pre-image und strong pre-image)
Iterieren, solange neue Zustände gefunden
player 0‘s turn
player 1‘s turn
lost for player 0
lost for player 1
Lösen von Zweipersonen-Nullsummenspielen
reach ← berechneErreichbareZustände()für jeden Spieler p ∈ {0, 1}
front ← verlorenp ← reach ∧ gewinn(p, 0) ∧ G ∧ zugp
gewonnen1-p ← reach ∧ gewinn(p, 0) ∧ G ∧ zug1-p
wiederhole
• pred ← pre-image(front) ∧ reach
• gewonnen1-p ← gewonnen1-p ∨ pred
• front ← strong-pre-image(gewonnen1-p) ∧ reach ∧ ⌐verlorenp
• verlorenp ← verlorenp ∨ front
solange front ≠ ⊥
Überblick 2 (Lösen allgemeiner Spiele) General Game Playing Einpersonenspiele Zweipersonenspiele
Zweipersonen-NullsummenspieleZweipersonenspiele mit allgemeinen Gewinnen
Lösen allgemeiner Zweipersonenspiele Mögliche Gewinne ∈ {0, …, 100} Verwendung von (101 x 101)-Matrix
Zustand an Position (i, j): i Punkte für Spieler 0 j Punkte für Spieler 1
falls unvollständig, Verwendung als Endspieldatenbank
Lösen allgemeiner Zweipersonenspiele Eine Vorwärts- und eine Rückwärtssuche
finde alle Vorgänger, deren Nachfolger alle gelöst sind (strong pre-image)
finde optimales Bucket für diese (pre-image)füge sie einiteriere, bis alle Zustände gelöst
Einschub: Reihenfolge beim Lösen
schwierig im allgemeinen Fall
eigenen Gewinn maximieren
(und gegnerischen minimieren)?
oder Differenz zum gegnerischen
Gewinn maximieren?
Hier: 2. Fall
own
100
0
opponent
0 100
…
…
own
100
0
opponent
0 100
…
…
Beispielplayer 0
play
er 1
0
0
1
1
2
2
3
3
player 0‘s turn
player 1‘s turn 0/1
0/1
0/3
2/0
3/1
2/0
2/0
3/1
3/1
0/1 3/13/1
0/10/1
0/3
0/3
0/1 3/1 0/1 2/0
0/10/1
0/1
Lösen allgemeiner Zweipersonenspiele
reach ← berechneErreichbareZustände()init matrix; solved ← alle Zustände in Matrixunsolved ← reach ∧ ⌐solvedsolange unsolved ≠ ⊥
für jeden Spieler p ∈ {0, 1}
• solvable ← strong-pre-image(solved) ∧ unsolved ∧ zugp
• falls solvable ≠ ⊥ matrix ← fügeZuständeEin(solvable, p, matrix) solved ← solved ∨ solvable unsolved ← unsolved ∧ ⌐solvable
Ergebnisse
Game t0-sum tnew
Clobber 3x4 - 1.1s
Clobber 3x4 0-sum 1.0s 1.4s
Clobber 4x5 - 2:14:20
Clobber 4x5 0-sum 0:54:35 1:22:09
Minichess 1.0s 0.7s
TicTacToe 0.1s 0.2s
Nim 40 0.0s 0.1s
Überblick
Wiederholung: BDDs BDD-basierte Suche BDD-BFS, BDD-Dijkstra, BDDA* Anwendung auf allgemeine Spiele („General Game
Playing“) BDDs als perfekte Hash-Funktion
Hashing
Gegeben: Menge von Zuständen S Gesucht: Abbildung S → R ⊆ ℕ Hashfunktion ordnet jedem Zustand einen Wert zu perfektes Hashing: Hashwert jedes Zustandes eindeutig minimales perfektes Hashing: |R| = |S|
Sat-Count
Anzahl gespeicherter Zustände in BDD G mögliche Berechnung:
sat-count(0-Senke) ← 0, sat-count(1-Senke) ← 1für Knoten v aus Schicht i mit 0-Nachfolger u in Schicht j > i und 1-
Nachfolger w in Schicht k > i sat-count(v) ← 2j-i-1 * sat-count(u) + 2k-i-1 * sat-count(w)
falls Wurzel in Schicht i: sat-count(G) ← 2i-1 * sat-count(Wurzel)
Laufzeit- und Speicherbedarf: ≤ O(|G|)
Sat-Count (Beispiel)
0 1
1
1
1
30
1614
4
3522
2
abgedeckte Zustände:000001 000111 001011 001101 010011 010100 010101 010110 010111 011011
011100 011101 011110 011111 100011 100100 100101 100110 100111 101011
101100 101101 101110 101111 110010 110011 110111 111010 111011 111111
Ranking
Gegeben: BDD G, Zustand s Gesucht: Hash-Wert von s (in {0, …, sat-count(G) - 1})
Vorverarbeitung:Berechne Sat-Count aller Knotenspeichere diese Sat-Counts
Ranking
rank(G,s)falls Wurzel in Schicht i
d ← Binärwert von (s1, …, si-1)
gib (d+1) * lexicographic-count(G,s,Wurzel) - 1 zurück
Ranking
lexicographic-count(G,s,v)falls v 0-Senke, gib 0 zurück; falls v 1-Senke, gib 1 zurückfalls v in Schicht i mit 0-Nachf. u in j und 1-Nachf. w in k
falls si = 0
• r0 ← lexicographic-count(G,s,u)
• d0 ← Binärwert von (si+1, …, sj-1)
• gib d0 * sat-count(u) + r0 zurück
falls si = 1
• r1 ← lexicographic-count(G,s,w)
• d1 ← Binärwert von (si+1, …, sk-1)
• gib 2j-i-1 * sat-count(u) + d1 * sat-count(w) + r1 zurück
Ranking (Beispiel)
s ← 011101rank(G,s) ← [()2 + 1] * lc(G,s,v0) - 1
lc(G,s,v0) ← ()2 * sc(v1) + lc(G,s,v1)
lc(G,s,v1) ← 23-2-1 * sc(v3) + (1)2 * sc(v6) + lc(G,s,v6)
lc(G,s,v6) ← 25-4-1 * sc(v9) + (01)2 * sc(v13) + lc(G,s,v13)
v13 ist 1-Senke → lc(G,s,v13) ← 1
lc(G,s,v6) ← 20 * sc(v9) + 1 * sc(v13) + lc(G,s,v13)
= 1 * 1 + 1 * 1 + 1 = 3lc(G,s,v1) ← 20 * sc(v3) + 1 * sc(v6) + lc(G,s,v6)
= 1 * 4 + 1 * 5 + 3 = 12lc(G,s,v0) ← 0 * sc(v1) + lc(G,s,v1) = 12
rank(G,s) ← 1 * lc(G,s,v0) - 1 = 110 1
1
1
1
30
1614
4
3522
2
v0
v1 v2
v3
v4 v5 v6 v7
v8 v9 v10
v11
v12 v13
Unranking
Gegeben: BDD G, Hash-Wert r Gesucht: zugehöriger Zustand
Unranking
unrank(G,r)starte an der Wurzelfalls Wurzel in Schicht l
(s1, …, sl-1) ← Binärrepräsentation von r div sat-count(Wurzel) r ← r mod sat-count(Wurzel)
v ← Wurzel; i ← lwiederhole, bis v 0- oder 1-Senke
falls v Knoten in Schicht i mit 0-Nachf. u in j 1-Nachf. w in k• falls r < 2j-i-1 * sat-count(u)
si ← 0; (si+1, …, sj-1) ← Binärrepräsentation von r div sat-count(u) r ← r mod sat-count(u) v ← u; i ← j
• falls r ≥ 2j-i-1 * sat-count(u) si ← 1; r ← r - 2j-i-1 * sat-count(u) (si+1, …, sk-1) ← Binärrepräsentation von r div sat-count(w) r ← r mod sat-count(w) v ← w; i ← k
Unranking (Beispiel)
r ← 19i ← 1; r ≥ 22-1-1 * sc(v1) = 1 * 14 = 14
s1 ← 1; r ← r - 22-1-1 * sc(v1) = 19 - 1 * 14 = 5
r ← r mod sc(v2) = 5 mod 16 = 5
i ← 2; r < 24-2-1 * sc(v6) = 2 * 5 = 10 s2 ← 0; (s3) ← (r div sc(v6))2 = (5 div 5)2 = 12 = 1
r ← r mod sc(v6) = 5 mod 5 = 0
i ← 4; r < 25-4-1 * sc(v9) = 1 * 1 = 1 s4 ← 0; r ← r mod sc(v9) = 0 mod 1 = 0
i ← 5; r ≥ 26-5-1 * sc(v12) = 2 * 0 = 0 s5 ← 1; r ← r - 27-5-1 * sc(v12) = 0 - 2 * 0 = 0
r ← r mod sc(v11) = 0 mod 1 = 0
i ← 6; r ≥ 27-6-1 * sc(v12) = 1 * 0 = 0 s6 ← 1; r ← r - 27-6-1 * sc(v12) = 0 - 1 * 0 = 0
r ← r mod sc(v13) = 0 mod 1 = 0
0 1
1
1
1
30
1614
4
3522
2
v0
v1 v2
v3
v4 v5 v6 v7
v8 v9 v10
v11
v12 v13
s ← s ← 1 s ← 101 s ← 1010 s ← 10101 s ← 101011
Ranking und Unranking (Analyse)
Vorverarbeitung: O(|G|) Ranking pro Zustand: O(n) Unranking pro Zustand: O(n) Vorverarbeitung beschriftet jeden Knoten mit n-bit Zahl →
O(n|G|) extra Bits nötig
Zusammenfassung
Symbolische Suche zur Verringerung der Speicherlastspeichern von Zustandsmengen (als BDDs) statt einzelner Zustände
Vorgänger- und Nachfolgerberechnungen (image und pre-image) liefern direkt SBFS und SBBFS
Symbolische Formen von Dijkstra und A* Lösen von Spielen mittels symbolischer Suche BDDs als perfekte Hash-Funktion