Regelbasierte Programmierung mit XL Winfried Kurth Reinhard Hemmerling BTU Cottbus, Lehrstuhl Grafische Systeme 1. PARADIGMEN DER PROGRAMMIERUNG Paradigma: grundlegendes Prinzip, beispielorientierte Vorstellung, zwischen "Modell" und "Analogie" angesiedelt, teilweise exakt, mathematisch unterstützbar, anschaulich, auf virtuellem Niveau. Paradigmen der Programmierung (nach Floyd 1979): imperatives Paradigma objektorientiertes Paradigma funktionales Paradigma Fallregel-Paradigma 1. Imperatives Paradigma (John von Neumann) liegt der klassischen imperativen Programmierung (BefehlsProgrammierung) zugrunde. Auch: "prozedurales Paradigma", "Kontrollfluss-Paradigma". Computer = Maschine zur Veränderung von Variablenwerten. Programm = Plan für den Berechnungsprozess mit Angabe der Befehle und des Kontrollflusses (z.B. Schleifen). Programmfindung: Elementare Einzelschritte finden und in flexible Reihenfolge bringen. Programmiersprachen: Fortran, Basic, Pascal, C, Teile von Java. Beispiel: x = 0; while (x < 100) x = x + 1; Inhalt der Variable x wird verändert Schleife legt Kontrollfluss fest Beachte: "=" steht hier nicht für math. Gleichheit, sondern für Zuweisung (prozesshaft)! 2. Objektorientiertes Paradigma Computer = Umgebung für virtuelle Objekte Programm = Auflistung von (Objekt-) Klassen, d.h. allgemeiner Spezifikationen von Objekten, die zur Laufzeit des Programms (ggf. mehrfach) erschaffen und wieder vernichtet werden können und miteinander kommunizieren. Programmfindung: Spezifikation der Klassen (Daten und Methoden), die Objektstruktur und -verhalten festlegen. Programmiersprachen: Smalltalk, Simula, C++, Delphi, Java (in den letzten 4 mit imperativen Konstrukten vermischt) Beispiel (in Java): public class Auto extends Fahrzeug { public String marke; public int plaetze; public void anzeigen() { System.out.println("Das Auto ist ein " + marke); System.out.println("Es hat " + plaetze + "Sitze."); } } typisch: Klassen (class) mit Daten (marke, plaetze) und Methoden (anzeigen) 3. Funktionales Paradigma (applikative Programmierung, McCarthy-Paradigma) Programmiersprachen: Lisp, Haskell, Lambda-Kalkül, APL Computer = Maschine, die Verallgemeinerungen von Operationen bilden, d.h. Funktionale definieren kann (vergleichbar der Bildung neuer Begriffe in der Mathematik) Programm = verschachtelter Ausdruck funktionaler Anwendungen Programmfindung: Spezifikation von Funktionen, die das Problem lösen Beispiel (FP-System nach Backus): def Skalarprod = (/+) ° (a*) ° trans definiert das Skalarprodukt zweier Vektoren beliebiger Dimension. Beachte: Keine Sequentialisierung, keine Variablendeklarationen, sehr kompakte Programme möglich. 4. Fallregel-Paradigma (van Wijngaarden, Lindenmayer) Computer = Transformationsmaschine für Strukturen oder für Zustände. Es gibt eine aktuelle Struktur, die solange transformiert wird, wie dies möglich ist. Arbeitsprozess: Such- und Anwendungsprozess. matching: Suchen einer passenden Regel, rewriting: Anwendung der Regel, um die Struktur umzuschreiben. Programm = Menge von Transformationsregeln. Programmfindung: Spezifikation der Regeln. Programmiersprachen: L-Systeme, XL, PROLOG, Intran, KISprachen. 2. L-SYSTEME (Lindenmayer-Systeme) analog zu Chomsky-Grammatiken, aber: in jedem Ableitungsschritt parallele Ersetzung aller Zeichen, auf die eine Regel anwendbar ist von A. Lindenmayer (Botaniker) 1968 zur Modellierung des Wachstums von fadenförmigen Algen eingeführt Chomsky-Grammatik für natürliche Sprache: Satz S P O S Max S Tina P lernt O Englisch O Französisch mögliche Ableitungen: Satz S Satz P O Max lernt Französisch S P O Tina lernt Englisch einfaches L-System: mathematisch: Ein L-System ist ein Tripel (, , R); darin ist: eine Menge von Zeichen, das Alphabet, eine Zeichenkette mit Zeichen aus , das Startwort (auch "Axiom"), R eine Menge von Regeln der Form Zeichen Zeichenkette; darin sind das Zeichen auf der linken Regelseite und die Zeichenkette aus entnommen. Ein Ableitungsschritt (rewriting) einer Zeichenkette besteht aus der Ersetzung aller Zeichen in , die in linken Regelseiten von R vorkommen, durch die entsprechenden rechten Regelseiten. Man vereinbart: Zeichen, auf die keine Regeln anwenbar sind, werden unverändert übernommen. Ergebnis zunächst nur: Ableitungskette von Wörtern, die sich durch wiederholte Anwendung des rewriting-Vorgangs aus dem Startwort ergeben. 1 2 3 .... Beispiel: Alphabet {A, B}, Startwort A Regelmenge R: AB B AB Ableitungskette: A B AB BAB ABBAB BABABBAB ABBABBABABBAB BABABBABABBABBABABBAB ... wie lang ist die n-te Zeichenkette in dieser Ableitung? was für die Modellierung von räumlichen Strukturen noch fehlt: eine geometrische Interpretation Füge also zur Def. eines L-Systems hinzu: eine Abbildung, die jeder Zeichenkette mit Zeichen aus eine Teilmenge des 3-dimensionalen Raumes zuordnet dann: "interpretierte" L-System-Abarbeitung 1 2 3 .... S1 S2 S3 .... S1, S2, S3, ... können als Entwicklungsstufen eines Objekts, einer Szene oder einer Konfiguration interpretiert werden. Als Interpretationsabbildung wird meistens gewählt: Turtle geometry ("Schildkrötengeometrie") befehlsgesteuertes, lokales Navigieren im 2D- oder 3DRaum (Abelson & diSessa 1982; vgl. Programmiersprache "LOGO") "Turtle": Zeichen- oder Konstruktionsgerät (virtuell) - speichert (grafische und nicht-grafische) Informationen - mit einem Zustandsspeicher assoziiert (wichtig für Verzweigungen) - aktueller Zustand der Turtle enthält z.B. Information über aktuelle Liniendicke, Schrittweite, Farbe, weitere Eigenschaften des als nächstes zu konstruierenden Objekts Der Turtle-Befehlsvorrat wird zu einer Untermenge der Zeichenmenge des L-Systems. Symbole, die nicht TurtleBefehle sind, werden von der Turtle ignoriert. Befehle (Auswahl): F0 "Forward", mit Konstruktion eines Elements (Linienstück, Segment, Gebäudetrakt...), benutzt wird die aktuelle Schrittweite für die Länge (die Null steht für "keine explizite Längenfestlegung") M0 forward ohne Konstruktion (Move-Befehl) L(x) ändere die aktuelle Schrittweite (Länge) zu x LAdd(x) inkrementiere die aktuelle Schrittweite um x LMul(x) multipliziere die aktuelle Schrittweite mit x D(x), DAdd(x), DMul(x) analog für die aktuelle Dicke RU(45) Drehung der turtle um die "up"-Achse um 45° RL(...), RH(...) analog um "left" und "head"-Achse up-, left- und head-Achse bilden ein rechtwinkliges, räumliches Koordinatensystem, das von der turtle mitgeführt wird RV(x)Rotation "nach unten" mit durch x vorgegebener Stärke Beispiel: L(100) D(3) RU(-90) F(50) RU(90) M0 RU(90) D(10) F0 F0 D(3) RU(90) F0 F0 RU(90) F(150) RU(90) F(140) RU(90) M(30) F(30) M(30) F(30) RU(120) M0 Sphere(15) erzeugt was ist das Ergebnis der Interpretation der Zeichenkette L(10) F0 RU(45) F0 RU(45) LMul(0.5) F0 M0 F0 ? Wiederholung von Abschnitten der Zeichenkette möglich mit dem Schlüsselwort "for" z.B. for ((1:3)) liefert ( A B C ) A B C A B C A B C was ist das Ergebnis der Interpretation von L(10) for ((1:6)) ( F0 RU(90) LMul(0.8) ) ? Verzweigungen: Realisierung mit Speicher-Befehlen [ lege aktuellen Zustand auf Speicher ("Ablage") ] nimm obersten Zustand von der Ablage und mache diesen zum aktuellen Zustand (damit: Ende der Verzweigung) Beispiel: Regeln A F0 [ RU(45) B ] A ; B F0 B ; Startwort L(10) A (A und B werden normalerweise nicht geometrisch interpretiert.) was für eine Struktur liefert das L-System A [ LMul(0.25) RU(-45) F0 ] F0 B; B [ LMul(0.25) RU(45) F0 ] F0 A; mit Startwort L(10) A ? was für eine Struktur liefert das L-System A [ LMul(0.25) RU(-45) F0 ] F0 B; B [ LMul(0.25) RU(45) F0 ] F0 A; mit Startwort L(10) A ? äquivalente Regel: A [ LMul(0.25) RU(-45) F0 ] F0 RH(180) A; Weitere Beispiele: Koch'sche Kurve: L(50) RU(90) A F0; A A LMul(0.3333); /* Skalierung */ F0 F0 RU(-60) F0 RU(120) F0 RU(-60) F0; jedes Linienstück wird durch 4 neue Linienstücke ersetzt (3. Regel); Skalierung durch Hilfssymbol A, welches sich in jedem Schritt reproduziert und dabei jeweils einen zusätzlichen Faktor 1/3 erzeugt (2. Regel). Das Startwort ist hier " ". Ausgabe nach 6 Schritten: Flächenfüllende Kurve: module R extends RU(-45); /* Vererbungsmechanismus */ module A extends F(10); Axiom ==> L(100) R X R A R X; X ==> X F0 X R A R X F0 X; Sierpinski-Dreieck (Realisierung als geschlossene Kurve, Verwendung von Hilfssymbol X für Insertion des inneren Dreiecks): L(50) RU(90) B F0 X F0 RU(-120) F0 F0 RU(-120) F0 F0; F0 F0 F0; X RU(-120) F0 X F0 RU(120) F0 X F0 RU(120) F0 X F0 RU(-120); B B LMul(0.5); Verzweigungsbeispiel: F0 F0 [ RU(25.7) F0 ] F0 [ RU(-25.7) F0 ] F0 ; Ergebnis nach 7 Schritten: (Startwort L(10) F0) Verzweigung, alternierende Zweigstellung und Verkürzung: L(10) F0 A ; A LMul(0.5) [ RU(90) F0 ] F0 RH(180) A ; welche Struktur liefert F(10) A ; A [ RU(-60) F(6) RH(180) A Sphere(3) ] [ RU(40) F(10) RH(180) A Sphere(3) ]; Sphere Z; ? (F(n) liefert Linie der vorgegebenen Länge n, Sphere(n) eine Kugel mit Radius n) Stochastische L-Systeme Verwendung von Pseudozufallszahlen Beispiel: deterministisch stochastisch float c = 0.7; float c = 0.7; Axiom ==> L(100) D(5) A; Axiom ==> L(100) D(5) A; A ==> F0 LMul(c) DMul(c) [ RU(50) A ] [ RU(-10) A ]; A ==> F0 LMul(c) DMul(c) if (probabiliy(0.5)) ( [ RU(50) A ] [ RU(-10) A ] ) else ( [ RU(-50) A ] [ RU(10) A ] ); Erzeugung einer Zufallsverteilung in der Ebene: Axiom ==> D(0.5) for ((1:300)) ( [ Translate(random(0, 100), random(0, 100), 0) F(random(5, 30)) ] ); Ansicht von oben schräg von der Seite Erweiterung des Symbol-Konzepts: Lasse reellwertige Parameter nicht nur bei Turtle-Kommandos wie "RU(45)" und "F(3)" zu, sondern bei allen Zeichen parametrische L-Systeme beliebig lange, endliche Parameterlisten Parameter werden bei Regel-Matching mit Werten belegt Beispiel: Regel A(x, y) F(7*x+10) B(y/2) vorliegendes Zeichen z.B.: nach der Regelanwendung: A(2, 6) F(24) B(3) Parameter können in Bedingungen abgeprüft werden (logische Bedingungen mit Java-Syntax): A(x, y) (x >= 17 && y != 0) .... Welche Struktur wird von folgendem L-System erzeugt? [ RU(90) M(1) RU(90) A(1) ] A(1); A(n) F(n) RU(90) A(n+1); Welche Struktur wird von folgendem L-System erzeugt? [ RU(90) M(1) RU(90) A(1) ] A(1); A(n) F(n) RU(90) A(n+1); Variante: in der zweiten Regel "RU(90)" etwa durch "RU(92)" ersetzen. Interpretationsregeln Einbau einer weiteren Regelanwendung unmittelbar vor der grafischen Interpretation (ohne Wirkung auf die nächste Generation) InterpretationsregelAnwendung Turtle-Interpretation Beispiel: public void run() { [ Axiom ==> A; A ==> Scale(0.3333) for (i:(-1:1)) for (j:(-1:1)) if ((i+1)*(j+1) != 1) ( [ Translate(i, j, 0) A ] ); ] applyInterpretation(); } public void interpret() [ A ==> Box; ] public void run() { [ Axiom ==> A; A ==> Scale(0.3333) for (i:(-1:1)) for (j:(-1:1)) if ((i+1)*(j+1) != 1) ( [ Translate(i, j, 0) A ] ); ] applyInterpretation(); } (a) (b) public void interpret() [ A ==> Box; ] A ==> Sphere(0.5); (c) A ==> Box(0.1, 0.5, 0.1) Translate(0.1, 0.25, 0) Sphere(0.2); was wird durch dieses Beispiel erzeugt? public void run() { [ Axiom ==> [ A(0, 0.5) D(0.7) F(60) ] A(0, 6) F(100); A(t, speed) ==> A(t+1, speed); ] applyInterpretation(); } public void interpret() [ A(t, speed) ==> RU(speed*t); ] Kontextsensitivität Abfrage eines Kontexts, der vorhanden sein muss, damit eine Regel anwendbar ist Angabe des Kontexts in (* .... *) Beispiel: module A(int age); module B(super.length, super.color) extends F(length, 3, color); Axiom ==> A(0); A(t), (t < 5) ==> B(10, 2) A(t+1); A(t), (t == 5) ==> B(10, 4); B(s, 2) (* B(r, 4) *) ==> B(s, 4); B(s, 4) ==> B(s, 3) [ RH(random(0, 360)) RU(30) F(30, 1, 14) ]; Der Schritt zu relationalen Wachstumsgrammatiken Nachteil von L-Systemen: • in L-Systemen mit Verzweigungen (über Turtle-Kommandos) nur 2 mögliche Relationen zwischen Objekten: "direkter Nachfolger" und "Verzweigung" Erweiterungen: • Zulassen weiterer Relationstypen (beliebig wählbar) • Zulassen von Zyklen ( Graph-Grammatik) • Grammatik modifiziert dann direkt den Graphen, Umweg über StringCodierung entfällt (bzw. wird nur noch für Regel-Input gebraucht) "relationale Wachstumsgrammatik" außerdem Nachteil der Turtle-Interpretation von L-Systemen: Segmente sind nur Zylinder, keine Objekte im Sinne der OOP Erweiterungen: • Knoten des Graphen können beliebige Objekte sein (auch Grafikobjekte) • Einbettung von Code einer höheren, imperativen oder objektorientierten Programmiersprache in die Regeln (für uns: Java) 3. RELATIONALE WACHSTUMSGRAMMATIKEN (RGG: Relational Growth Grammars) allgemeiner Aufbau einer Regel einer RGG: eine RGG-Regel und ihre Anwendung in grafischer Form: Regel: Anwendung: Kanten-Markierungen repräsentieren verschiedene Arten von Relationen: • ist Nachbar von • enthält • trägt • codiert (genetisch) • ist gepaart mit • (...) auch möglich: Darstellung von multiskalierten Strukturen RGG als Verallgemeinerungen von L-Systemen: Zeichenketten entsprechen speziellen Graphen In Textform schreiben wir allgemeine Kanten als -kantensorte-> Kanten des speziellen Typs "Nachfolger" werden als Leerzeichen geschrieben (statt -successor->) Sonderformen von RGG-Regeln: Aktualisierungsregeln (Regelpfeil ::> ): es werden nur Parameter verändert Instanzierungsregeln: einzelne Zeichen werden in Substrukturen aufgelöst, ohne Einfluss auf den nächsten Entwicklungsschritt Realisierung in einer Programmiersprache: Sprache XL (eXtended L-system language) • RGG-Regeln in Blöcken organisiert Kontrolle der Reihenfolge der Regelanwendungen • Turtle-Kommandos als Knoten erlaubt • Knoten sind Java-Objekte • Sprache Java als Rahmen für die gesamte RGG Benutzer kann Konstanten, Variablen, Klassen... definieren • globale Sensitivität, graph queries Beispiel: Wachstum nur, wenn genügender Abstand zu anderen F-Objekten module A(int s); Axiom ==> F(100) [ RU(-30) A(70) ] RU(30) A(100); a:A(s) ==> if ( forall(distance(a, (* F *)) > 60) ) ( RH(180) F(s) [ RU(-30) A(70) ] RU(30) A(100) ) ohne die Bedingung mit der Bedingung XL wird "verstanden" von der interaktiven 3D-Plattform GroIMP (Growth-grammar related Interactive Modelling Platform) • GroIMP stellt Objekte für die 3D-Visualisierung bereit. Diese können in XL verwendet werden. • GroIMP ist ein open source-Projekt; siehe http://www.grogra.de. Beispiel eines mit GroIMP realisierten Pflanzenmodells (Gerste):