Regeln und Koordination
Prozessabbild
Das Prozessabbild enthält die aktuellen Werte der Sensoren und
die Sollwerte für die Aktoren. In einem starren zeitlichen
Ablauf werden die Sensorwerte einmalig zu Beginn des Zyklus in
das Prozessabbild eingetragen. Das verhaltensbasierte Programm
arbeiten mit einer Momentaufnahme der Umwelt. Während eines
Durchlaufs durch die Regeln ändern sich zwar die realen
Umweltbedingungen, aber im Prozessabbild bleiben die gemessenen
Werte innerhalb eines Zyklus konstant. Zum Abschluss des Zyklus
werden die Sollwerte in das Prozessabbild geschrieben und von
dort auf die Aktoren übertragen
Regeln
Regeln bilden die Grundbausteine für das Verhalten des
Roboters. Durch sie werden die Messwerte der Sensoren auf die
Aktoren abgebildet. Intern bestehen sie aus arithmetischen
Ausdrücken, bedingten Anweisungen und Funktionsaufrufen. Die
Regeln lesen die Sensorwerte, führen ihre logischen
Entscheidungen und Berechnungen durch und leiten die Sollwerte an
die Aktoren weiter. Zur Laufzeit können Regeln nicht geändert
werden.
Mehrere Regeln dürfen auf den selben Aktor einwirken. Dabei
liefern die Regeln höchstwahrscheinlich widersprüchliche
Sollwerte. Eine Regel könnte zum Beispiel in Richtung einer
Lichtquelle steuern, während eine Andere die Motoren stoppen
will. In einem verhaltensbasierten System ist dies kein
fehlerhafter Zustand, sondern der Normalfall. Die Widersprüche
werden von den, im nächsten Abschnitt beschriebenen,
Koordinatoren ausgeglichen.
Beispiel: Zwei Regeln melden ihre Wünsche für die Motoren
an:
# 1. Regel: Anteil für geradeaus fahren
rule driveForward:
self.SpeedLeft = 120
self.SpeedRight = 120
# 2. Regel: Anteil für die Kurve
rule driveCurve:
if curve > 0:
self.SpeedLeft = 0
self.SpeedRight = -40
Koordination
Für die Koordination der unterschiedlichen Vorgaben der
Regeln gibt es mehrer Ansätze. Sie unterscheiden sich dadurch
wie viel Koordinatoren mitarbeiten und ob diese eher kooperativ
oder elitär entscheiden.
Anzahl der Koordinatoren
- Ein zentraler Koordinator pro Aktor
- Mehrere dezentrale Koordinatoren. Diese sind jeweils für
eine Untermengen der Regeln zuständig.
Strategien für Koordination
- Kooperative Strategien: Summation oder Mittelwertbildung
der beteiligten Regeln, geeignet für kontinuierliche
Funktionen
- Prioritäten: Eine der Regeln setzen sich mit Hilfe ihrer
hohen Prioritäten durch. Bezeichnet wird diese Strategie als
"winner takes all". Die Regel mit der höchsten Priorität
setzt sich alleinig durch.
- Gewichte: Die Ergebnisse der Regeln werden
situationsbedingt gewichtet. Zum Beispiel könnte verbleibende
Ladungskapazität des Akkus für die Gewichtung der Regeln
eine Rolle spielen.
Welche Koordinationsstrategie gewählt wird, hängt von der
Aufgabenstellung ab. Es kann keine ideale Strategie für alle
verhaltensbasierte Aufgabenstellungen angeboten werden. Der
Koordinator wird deshalb vom Anwender in der
Vehikel-Programmiersprache selbst formuliert.
Beispiel: Mittelwertbildung der beteiligten Regeln:
arbiter average(array rules):
var count = 0
var left = 0
var right = 0
for r in rules:
left = left + r.SpeedLeft
right = right + r.SpeedRight
count = count + 1
assert count > 0
self.SpeedLeft = left / count
self.SpeedRight = right / count
Dezentrale Koordination
Zusätzlich ist es möglich, Koordinatoren, die nur eine
Untermenge der Regeln ausgleichen, zu definieren. Die Regeln
können gruppiert werden. Pro Regelgruppe wird dann ein
Koordinator formuliert. Dieser lokale Koordinator kann nur
zwischen den Regeln seiner Gruppe ausgleichen. Sein Ergebnis
konkurriert mit den anderen Regelgruppen auf der nächst
äußeren Stufe und wird vom dortigen Koordinator ausgeglichen.
Das Gesamtsystem besteht dann aus mehreren Regeln, die gruppiert
sind und lokal pro Gruppe koordiniert werden. Die einzelnen
Koordinatoren müssen nur jeweils eine Strategie implementieren.
Regeln und Koordinatoren bleiben dadurch übersichtlich.
Im folgenden Programmbeispiel sind die Regeln inner21 und inner22 zur Gruppe outer2 zusammengefasst. Konflikte
zwischen inner21 und
inner22 werden vom
Koordinator vectorAddition ausgeglichen. Er soll
in diesem Beispiel eine kooperative Strategie verfolgen, indem er
die Sollwerte der konkurrierenden Regeln mittelt. Die äußeren
Regeln sind für ihn nicht sichtbar. Der Koordinator vectorAddition liefert den Wunsch
für outer2 nach außen.
Dort tritt er in Konkurrenz zu Regel outer1, welche in diesem Beispiel
für den Nothalt des Systems zuständig ist. Der Koordinator
winnerTakesAll trifft
dann, ganz unkooperativ, eine 1 aus n Entscheidung zwischen
Normalbetrieb und Nothalt.
rule outer1:
....
rule outer2:
rule inner21:
....
rule inner22:
....
arbiter vectorAddition(array rules):
....
arbiter winnerTakesAll(array rules):
....
Zeit
In dieser Implementierung wird von einer endlichen
Bearbeitungszeit für die Regeln und Koordinatoren ausgegangen.
Pro Zyklus werden alle aktiven Regeln ausgeführt. Somit liefert
jede dieser Regeln ihren Beitrag an die Koordinatoren ab. Dies
zieht jedoch die Einschränkung nach sich, dass innerhalb einer
Regel oder eines Koordinators keine Konstrukte, die zu einer
beliebig langen Rechenzeit führen, erlaubt sind.
Von außen betrachtet laufen die Regeln zeitlich parallel ab und
wirken ohne Zeitverzögerung. In einem realen Einprozessorsystem
werden die Regeln natürlich sequenziell abgearbeitet. Im
Unterschied zum biologischen Vorbild ist es nur möglich, eine
Quasiparallelisierung zu erreichen. Die Reihenfolge ist zwar
reproduzierbar, aber für den Anwender nicht offensichtlich. Beim
Entwurf von Regeln sollte der Programmierer deshalb keine Annahme
bezüglich der Reihenfolge ihrer Bearbeitung einzelner Regeln
voraussetzen.
Verzögerungsglieder
Das ablaufen der Zeit kann mit Verzögerungsgliedern
modelliert werden. Der Wert eines Verzögerungsglied wird, nach
dem es gestartet wurde, pro Zyklus um eins erniedrigt bis es Null
erreicht. Im folgenden Beispiel soll der Roboter über ein
Schalter verfügen, um Kollisionen mit der Wand zu erkennen. Wird
dieser gedrückt fährt der Roboter blind zurück.
system:
configuration .....
delay backward = 20
# Vorwärts bis ''FrontBumper'' eine Kollision meldet
rule drive:
if FrontBumper:
backward.trigger()
else:
self.SpeedLeft = 50
self.SpeedRight = 50
# Blind rückwärts fahren mit Kurve.
rule back:
if backward > 0:
self.SpeedLeft = -10
self.SpeedRight = -30
# Addition der Sollwerte aus den beiden Regeln
arbiter vectorField(array rules):
self.SpeedLeft = 0
self.SpeedRight = 0
for r in rules:
self.SpeedLeft = self.SpeedLeft + r.SpeedLeft
self.SpeedRight = self.SpeedRight + r.SpeedRight