5. Lösungssuche


[ Constraint Logic Programming Seminar ] ... [ Thema Stundenplangenerierung mit CLP ] ... [ 6. Weiche Constraints ]

Übersicht


5.1. Allgemein

Um eine Lösung gerichteter zu suchen, als mit dem unter 4.5. beschriebenen Depth-First-Ansatz mit dem primitiven labeling Prädikat werden Heuristiken verwendet. Mit Heuristiken wird das Durchsuchen des Suchraums gezielt gesteuert. Für CLP bedeutet das, dass im labeling nach gewissen Kriterien gezielt gesteuert wird,

5.2. Steuerung der Reihenfolge der Problemvariablen

Um die Reihenfolge der Problemvariablen zu steuern, wird die Liste der Problemvariablen nach den gewünschten Kriterien sortiert. Zum Beispiel könnte es sinnvoll sein, beim labeling zuerst die Veranstaltungen in den Stundenplan zu legen, welche die grössten Teilnehmerzahlen haben, da diese Veranstaltungen in der Regel leichter Konflikte (und somit Backtracking beim labeling) auslösen, als Veranstaltungen mit geringeren Teilnehmerzahlen. Wenn man davon ausgehen kann, dass alle Kriterien nach denen sortiert werden soll, durch einen gültigen Prologausdruck beschreiben lassen (so lässt sich das Kriterium Teilnehmeranzahl durch einen numerischen Wert ausdrücken), dann besteht die Sortierung der Liste der Problemvariablen aus vier Schritten:

Beispiel:
Sortierung der Problemvariablen nach Teilnehmeranzahl und zur Verfügung stehenden Räume:

main :-
  ...
  findall([TeilnehmerAnz0, RaumAnz, VeranstaltungsID], 
(veranstaltung(VeranstaltungsID, Teilnehmer, BenRessourcen),
zaehleTeilnehmer(Teilnehmer, TeilnehmerAnz),
TeilnehmerAnz0 is TeilnehmerAnz * (-1), findall(RaumID, raum(RaumID, _, _, _), RaumListe),
holeGueltigeRaeume(RaumListe, VeranstaltungsID, GueltigeRaeume),
length(GueltigeRaeume, RaumAnz)
),
SortKriteriumsListe
),
sort(SortKriteriumsListe, SortListe),
mkVerIDListe(SortListe, SortVerIDListe), sortiere(ProblemVars, SortVerIDListe, SortiertePVars),
labeling(SortiertePVars),
... mkVerIDListe([], []). mkVerIDListe([SortElement|SortRest], [VerID|VerIDRest]) :- letztes(VerID, SortElement) mkVerIDListe(SortRest, VerIDRest). letztes(LetztesEle, [LetztesEle]). letztes(LetztesEle, [_|Rest]) :- letztes(LetztesEle, Rest). sortiere(_, [], []). sortiere(PVars, [VerID|VIDRest], [Element|ErgebnisListe]) :- suchen(PVars, VerID, Element), sortiere(PVars, VIDRest, ErgebnisListe). suchen([raumTagZeit(Raum, Tag, Zeit, VerID)|PRest], VerID, raumTagZeit(Raum, Tag, Zeit, VerID)). suchen([PVar|PRest], VerID, Element) :- suchen(PRest, VerID, Element).
Die Wirkung dieses Algorithmusses soll an einem Beispiel deutlich gemacht werden:
Reihenfolge der Problemvariablen:
[raumTagZeit(Raum, Tag, Zeit, bwl1), raumTagZeit(Raum, Tag, Zeit, fourier_laplace), raumTagZeit(Raum, Tag, Zeit, analysis1_1)]
SortKriteriumsListe nach findall:
[[-60, 5, bwl1], [30, 5, fourier_laplace], [-60, 3, analysis1_1]]
SortListe nach dem sort:
[[-60, 3, analysis1_1], [-60, 5, bwl1], [30, 5, fourier_laplace]]
SortVerIDListe nach dem mkVerIDListe:
[analysis1_1, bwl1, fourier_laplace]
Reihenfolge der Problemvariablen nach dem sortiere:
[raumTagZeit(Raum, Tag, Zeit, analysis1_1), raumTagZeit(Raum, Tag, Zeit, bwl1), raumTagZeit(Raum, Tag, Zeit, fourier_laplace)]

Was diesen Algorithmus auszeichnet ist, dass zum Wechseln der Heuristik nur der findall-Ausdruck ausgewechselt werden muss, da die Sortierprädikate heuristikunabhängig arbeiten. Aus diesem Grund wird das Tupel aus Sortierkriterien auch nicht als Struct, sondern als Liste abgebildet.

5.3. Steuerung der Reihenfolge der Wertebelegung

Die Steuerung der Reihenfolge, in welcher den Domainvariablen Raum, Tag und Zeit Werte aus ihren Wertebereichen zugewiesen werden, lässt sich durch Überschreiben des in der FiniteDomain Library vorgegebenen Prädikats indomain realisieren. Das vorgegebene Prädikat indomain/1 macht nichts anderes, als die Werte aus der Domain einer Problemvariablen in der Reihenfolge auszuprobieren, in der sie in der Domain vorliegen und das ist bei Domains aus numerischen Werten die numerische Reihenfolge.
Beispiel:
Tag::1..5
Reihenfolge beim Zuweisen mit indomain/1: 1, 2, 3, 4, 5
In einem selbstdefinierten indomain Prädikat kann man den Wertebereich der Domainvariablen umsortieren, um den Wertebereich in einer dem Problem mehr angepassten Ordnung, als der der numerischen Grösse der Werte, auszuprobieren. So kann es zum Beispiel bei der Auswahl von Werten für die Domainvariable Raum sinnvoll sein, zunächst Räume zu wählen, bei denen die Differenz zwischen dem angebotenen Platz und dem Platz den die Teilnehmer der entsprechenden Veranstaltung belegen, möglichst gering ist. Dadurch wird die Wertebelegung dieser Variable von Anfang an in Richtung einer ökonomischen Ressourcennutzung gesteuert. Dazu wird mit dem von der FiniteDomain Library vorgegebenen Prädikat dom/2 der Wertebereich einer Domainvariablen auf eine Liste abgebildet. Für jeden Wert dieser Liste werden ein oder mehrere Bewertungswerte, in Abhängigkeit des/der Kriterums/en nach denen die Auswahl der Werte erfolgen soll, generiert. Nach diesen Kriteriumsbewertungwerten kann dann die Liste der Werte des Wertebereichs der Domainvariablen sortiert werden. Durch das vorgegebene Prädikat member, wird dann die Domainvariable mit dem erstmöglichen Wert dieser umsortierten Liste instanziiert.
Beispiel:
Veranstaltungen sollen bei der Suche im Suchraum immer zunächst in Räume gelegt werden, bei denen die Differenz zwischen dem Platz, den sie zur Verfügung stellen, und dem Platz, den die Teilnehmer der entsprechenden Veranstaltung belegen, möglichst gering ist.

labeling([ProblemVar|Rest]) :-
  ProblemVar = raumTagZeit(Raum, _, _, VeranstaltungsID),
  veranstaltung(VeranstaltungsID, Teilnehmer, _),
  zaehleTeilnehmer(Teilnehmer, TeilnehmerAnzahl),
  dom(Raum, RaumListe),
  mkRaumKriteriumsListe(RaumListe, TeilnehmerAnzahl, KriteriumsListe),
  indomain(Raum, KriteriumsListe),
  indomain(Tag),
  indomain(Zeit),
  labeling(Rest).


mkRaumKriteriumsListe([], _, []).
mkRaumKriteriumsListe([RaumID|RRest], TeilnehmerAnzahl, [Kriterium|KRest]) :-
  raum(RaumID, Platz, _, _),
  KriteriumsInhalt is Platz - TeilnehmerAnzahl,
  Kriterium = [KriteriumsInhalt],
  mkRaumKriteriumsListe(RRest, TeilnehmerAnzahl, KRest).  


indomain(DomainVar, _) :-
  nonvar(DomainVar).
indomain(DomainVar, KriteriumsListe) :-
  var(DomainVar),
  dom(DomainVar, Wertebereichsliste),
  sortiereWerte(Wertebereichsliste, KriteriumsListe, SortierteWertebereichsListe),
  member(DomainVar, SortierteWertebereichsListe).


sortiereWerte(Wertebereichsliste, KriteriumsListe, SortierteWertebereichsListe) :-
  mkKombiListe(Wertebereichsliste, KriteriumsListe, KombiListe),
  sort(KombiListe, SortierteKombiListe),
  mkWerteListe(SortierteKombiListe, SortierteWertebereichsListe).


mkKombiListe([], [], []).
mkKombiListe([Wert|WRest], [Kriterium|KRest], [KombiEle|KombiRest]) :-
  append([Wert], Kriterium, KombiElement),
  mkKombiListe(WRest, KRest, KombiRest).


mkWerteListe([], []).
mkWerteListe([Kriterium|SKRest], [Wert|SWRest]) :-
  letztes(Wert, Kriterium),
  mkWerteListe(SKReste, SWRest).
Die Wirkung dieses Algorithmusses soll an einem Beispiel deutlich gemacht werden:
Die Domain der Domainvariablen Raum sei für die Veranstaltung analysis1_1:
Raum :: 1..5 Die TeilnehmerAnzahl sei: 100
Die Liste RaumListe nach dem dom im labeling ist:
[1, 2, 3, 4, 5]
Die KriteriumsListe nach mkRaumKriteriumsListe: [0, 100, 50, 50, 150]
das heisst nach der benutzten Wissensbasis hat Raum 1 genau 100 Plätze, Raum 2 200 Plätze, Raum 3 und 4 150 Plätze und Raum 5 250 Plätze.
Die Wertebereichsliste nach dem dom im indomain:
[1, 2, 3, 4, 5]
Die SortierteWertebereichsListe nach dem sortiereWerte im indomain:
[1, 3, 4, 2, 5]

Auch dieser Algorithmus erlaubt es, die verwendete Heuristik sehr einfach zu ändern. Das überschriebene indomain Prädikat funktioniert für alle möglichen Heuristiken, nur im labeling-Prädikat muss entsprechend geändert werden.

5.4. Häufig benutzte Heuristiken

Es gibt eine Menge von Heuristiken, die eine oder beide Möglichkeiten der Suchsteuerung ausnutzen, auch wenn keine dieser Heuristiken das durch jahrelange manuelle Stundenplanlegen gewonnene Expertenwissen simulieren kann. Die Effizienz dieser Heuristiken lässt sich nur begrenzt qualifizieren, da Heuristiken, wie schon an sehr einfachen Suchproblemen (wie z.B. das N-Damen Problem) gezeigt werden kann, für unterschiedliche Instanzen eines Problems unterschiedlich gut geeignet sind. Manchmal genügt schon eine geringfügige Veränderung des Suchraums (z.B. durch Vergrösserung oder Verkleinerung der Menge der Problemvariablen), um aus einer sehr effizienten Heuristik eine völlig ungeeignete zu machen. Trotzdem wurden durch empirische Ergebnisse gestützte Erfahrung einige Prinzipien gewonnen, um Heuristiken zu bewerten. Für die Steuerung der Reihenfolge in der die Veranstaltungen in den Stundenplan gelegt werden sind u.a. folgende Heuristiken aus der Literatur bekannt: Kritik:
Die Gefahr des naiven und des first-fail-Ansatzes besteht darin, dass konfliktträchtige Veranstaltungen erst recht spät in der labeling-Phase verarbeitet werden könnten, so dass dann schon die wenigen Positionen, die für diese Veranstaltungen möglich wären, belegt sind und diese Fehler durch viel Backtracking rückgängig gemacht werden müssen.

Für die Steuerung der Reihenfolge, in der die Veranstaltungen in den Stundenplan gelegt werden, sind u.a. folgende Heuristiken aus der Literatur bekannt: Kritik:
Bei dem naiven Ansatz werden die Veranstaltungen von Anfang an sukzessive vom Anfang bis Ende der Woche belegt. Das führt dazu, dass Konflikte erst relativ spät beim labeling entdeckt werden und durch viel Backtracking rückgängig gemacht werden müssen. (Bericht von [5]). Die anderen Strategien legen die Veranstaltungen nicht in einer festen Reihenfolge in Räume und Zeitfenster, so dass diese Gefahr nicht besteht.


[ Constraint Logic Programming Seminar ] ... [ Thema Stundenplangenerierung mit CLP ] ... [ 6. Weiche Constraints ]