Eine Einführung in das EcliPSe System


... [ Seminarthemen SS01 ] ... [ Inhaltsverzeichnis ] ... [ Ressourcen ] ...

Fallbeispiel: Das Damenproblem als Veranschaulichung der Verwendung von Heuristik innerhalb von Suchstrategien


Einleitung

Anhand des bekannten Damenproblems (N-Queens-Problem) wird eine kleine Einführung in die Programmierung in ECLiPSe mit der FD-Library gegeben. Dabei wird das Problem mit unterschiedlichen Heuristiken angegangen und die Ergebnisse diskutiert.

Die Aufgabenstellung

Wie muß man N Damen auf einem Schachbrett der Ausmaß NxN plazieren, damit sich keine zwei Damen gleichzeitig bedrohen.


Lösung per Hand

Offensichtlich darf auf jeder Spalte, jeder Zeile und jeder Diagonalen nur eine Dame stehen.
Ein solches Problem, welches nur über binäre Relationen verfügt, kann gut als Graph dargestellt werden. Einen solchen Graphen bezeichnet man dann als Constraint Network. Als Beispiel folgt hier die Darstellung des Constraint Networks für das 4-Damen Problem.


Die Kanten sind jeweils mit mit den Wertepaaren beschriftet, die der Constraint "DameX und DameY bedrohen sich nicht" genügen.

Anhand dieses Netzwerkes läßt sich nun ein Suchbaum konstruieren, der zu den Lösungen führt:


Die Lösung erfolgt nach mittels inkrementeller Zuordnung (Constructive Search), deren Methodik im vorangegangenen Kapitel schon näher beschrieben wurde. Bei jeder Wertzuweisung müssen die Constraints zwischen aktueller und bereits zugewiesenen Variablen beachtet werden. So erhält man die beiden einzigen Lösungen für das 4-Damen Problem. Schon bei dieser kleinen Dimensionierung fällt auf, daß die Lösungen in der Mitte des Suchbaumes liegen, womit sich das Problem als gutes Anschauungsobjekt für den Einsatz von Heuristiken anbietet.


Ein ECLiPSe Programm zur Lösung

:- lib(fd).
Hiermit wird die Finite Domain Library eingebunden
queens(N, Board) :-
Der Klauselkopf. Mit N wird die Größe des Schachbrettes spezifiziert. Board ist ein Platzhalter, damit man vom Prolog Interpreter die Lösung erhält und nicht nur die Aussage, daß es eine gibt.
length(Board,N),
Das vordefinierte Prädikat length/2 erzeugt bei dieser Art der Verwendung eine Liste mit N Variablen.
Board :: 1..N,
Hiermit wird der Wertebereich für die Listenvariablen festgelegt
	
( fromto (Board, [Q1|Cols], Cols, []) do
  ( foreach(Q2, Cols), count(Dist,1,_), param(Q1) do
      noattack(Q1, Q2, Dist)
  )
),
    
Verschachtelte Schleifenprogrammierung in ECLiPSe unter Vermeidung von Symmetrie. Dem Prädikat noattack/3 werden die nötigen Parameter übergeben: Die beiden Damen Variablen und die Distanz in Spalten zueinander. Aus diesen Informationen definiert das Prädikat die erforderlichen Constraints
labeling(Board).
Die Standard Suchmethode von ECLiPSe wird aufgerufen
noattack(Q1,Q2,Dist) :-
Der Klauselkopf für die Generierung der Constraints
	Q1 ## Q2,
Die Damen dürfen nicht auf einer gemeinsamen Zeile positioniert werden
   	Q1 - Q2 ## Dist,
   	Q2 - Q1 ## Dist.
       
Die Damen dürfen nicht auf einer gemeinsamen Diagonalen positioniert werden
Wenn man dieses Programm nun mit verschiedenen Instanzen ausführt, macht sich selbst auf einem derzeit aktuellen System (PIII, 933 MHZ) schnell Ernüchterung breit. Beim 16-Damen Problem wird noch ohne merkbare Verzögerung eine Lösung gefunden, bei einem 32x32 Feld ist geduldiges Warten angesagt. Um diese Performance zu steigern, werden im folgenden Heuristiken angewendet, die im vorangegangenen Kapitel näher beschrieben wurden.


Optimierung durch die Anwendung von Heuristiken

Ausgangspunkt war die Standardsuchroutine, die mit dem vordefinierten Prädikat labeling/1 aufgerufen wurde. Intern ist dieses Prädikat wie folgt aufgebaut:

   labeling(AllVars) :- 
   	( foreach(Var, AllVars) do
   		indomain(Var)
   	).
 
Das vordefinierte Prädikat indomain(Var) ordnet der Variable Var einen Wert aus deren Wertebereich zu. Dabei wird mit dem numerisch kleinsten Wert angefangen. Beim Zurückgehen im Suchbaum werden sukzessive Werte zugewiesen.
Bei der Diskussion der Suchstrategien wurde im Rahmen der Heuristik das Prinzip erwähnt, die Variablen mit der kleinsten Wertebereichsausprägung oben im Suchbaum anzuordnen, um die Anzahl der Knoten zu minimieren.
Um dieses Prinzip zu implementieren, wird das labeling/1 Prädikat folgendermaßen verändert:

   labeling(AllVars) :- 
   	( fromto(AllVars, Vars, VarsRem, []) do
   		deleteff(Var, Vars, VarsRem),
   		indomain(Var)
   	).
 
Das Prädikat deleteff(Var,Vars,VarsRem) wählt aus der Liste List die Variable Var mit der geringsten Wertebereichsausprägung und gibt den Rest der Liste in VarsRem zurück.
Das Ergebnis beeindruckt. Mußte man bei der Standardmethode noch stundenlang warten, so wird bei Anwendung dieses Prädikates die erste Lösung bereits nach einer Millisekunde gefunden. Auch die erste Lösung für das 64-Damen Problem läßt nicht lange auf sich warten (0.14 s CPU)

Diese Heuristik läßt sich jedoch noch optimieren. Schon bei der Lösung per Hand wurde deutlich, daß die Lösungen in der Mitte des Suchbaumes zu finden sind. Dabei handelt es sich um eine problemspezifische Ausprägung, die darauf zurückzuführen sind, daß Figuren in der Mitte eines Schachbrettes den größten Wirkungskreis haben, also die meisten Felder abdecken können. Dieser Umstand, passionierten Schachspielern kein Geheimnis, ist die Grundlage für die folgende Optimierung:

:- lib(list).

labeling(AllVars) :-
		middle_first(AllVars, AllVarsPreOrdered),
		( fromto(AllVarsPreOrdered, Vars, VarsRem,[]) do 
			deleteff(Var,Vars,VarsRem),			
			indomain(Var)
                ).

middle_first(List,Ordered) :- 
	halve(List, Front, Back),
	reverse(Front,RevFront),
	splice(Back,RevFront,Ordered).
Diese Implementierung benötigt ein paar spezielle Funktionen aus der Library list zur Listenmanipulation. Die Vorgehensweise liegt in einer Vorsortierung der Wertebereiche durch das Prädikat middle_first. Die übergebene Liste wird zweigeteilt, der Kopf umgedreht und dann mit splicewieder zusammengefügt. Das Zusammenfügen mit splice erfolgt durch abwechselnde Konkatenierung. Ein Beispiel verdeutlicht das Vorgehen:

list = [1,2,3,a,b,c]

Nach Anwendung von halve:
Front = [1,2,3] , Back = [a,b,c]

Nach Anwendung von reverse:
RevFront = [3,2,1]

Die Anwendung von splice:
splice([a,b,c],[3,2,1],[a,3,b,2,c,1])

Damit werden also bei der Belegung die mittleren Variablen zuerst berücksichtigt.
Denkbar wäre jetzt noch eine Heuristik, die nicht nur zuerst die mittleren Spalten, sondern ebenfalls die mittleren Zeilen zu belegen. Im Rahmen dieser Ausarbeitung wird jedoch auf eine Diskussion der Umsetzung verzichtet, als Weiterführung sei das der ECLiPSe Distribution beiligende Tutorial on Search in ECLiPSe genannt.

Nach oben

... [ Seminarthemen SS01 ] ... [ Inhaltsverzeichnis ] ... [ Ressourcen ] ...