Übung 2 zu Verteilte und Parallele Programmierung: Zelluläre Automaten (Teil 1) (PD Stefan Bosse) [4.2024] |
Mit dieser Übung soll eine erste Einarbeitung mit Lua in ein bekanntes paralleles und verteiltes Berechnungsmodell und Architektur stattfinden: Dem Zellulären Automaten.
In dieser Übung wird ein ZA durch sequentielle Berechnung simuliert. In folgenden Übungen wird die Simulation durch Partitionierung der Simulationswelt parallelisiert.
Der Transpiler besteht im wesentliche aus einem Lua Parser (https://github.com/fstirlitz/luaparse) der Esprima AST Bäume erzeugt (https://github.com/jquery/esprima), und schließlich mit Escodegen (https://github.com/estools/escodegen) in JavasScript Code umgewandelt wird. Der JavaScript Code wird schließlich ausgeführt.
arr:keys():join(',')
⇒ local k=arr:keys(); local s=kjoin(',')
Dieses interaktive Notebook enthält statische Inhalte und Code Snippets die direkt im Notebook ausgeführt werden können
In Code Snippets: Linker Play Knopf startet die Ausführung des Codes. Der rechte Knopf ermöglicht das Löschen der Ausgabe!
Bei Animationen gibt es es noch einen Stop Knopf der zur unmittelbaren Terminierung der Animation führt.
Der Code kann verändert werden!
Alle editierbaren Inhalte können gespeichert werden: Rechts oben (Save Notebook) Knopf
Alle editierbaren Inhalte können wieder geladen werden und überschreiben Inhalte: Links oben (Load Notebook) Knopf
Es können Notizen in Markdown Syntax gemacht werden, die auch gespeichert werden (Oben, Stift Knopf) oder Sticky Notes einfügren
Der Code ist in Lua - eine Einführung gibt es hier luaTutorial.html
Ein zellulärer Automat besteht aus einer Menge von zustandsbasierten Zellen ZA={z1,z2,..,zn}.
Eine Zelle besitzt eine Anzahl von Zellenvariablen die den veränderlichen (Daten) Zustand S bestimmen: S(zi)={x1,..,xk}
Der nächste Zustand der Zellen wird über eine activity Funktion berechnet.
Der Zustandsübergang einer Zelle erfolgt i.A. in der before oder after Funktion.
Ein Scheduler führt die einzelnen Berechnungsfunktionen einer Zelle sequenziell in drei Phasen aus (obwohl ein ZA ein parelleles System ist!):
Zellulärer Automat als Netzwerk aus einfachen kommunizierenden Berechnungseinheiten
Die Definition eines ZA Modells mit calua Bibliothek erfolgt in zwei Schritten:
Das Weltmodell ist ein Gitternetzwerk mit farbigen Kacheln für die grafische Visualisierung
Eine Zellenklasse besitzt zwei wichtige Parameter: name und state
-- 1. Leere Zellenklasse erzeugen
local cell=CA.Cell({
name = S,
state = { var = ε, .. }
})
Eine Zellenklasse muss mindestens drei Methoden definieren:
Optional gibt es die Methoden:
-- 2. Eigene Methoden hinzufügen
function cell:initialize (x,y)
..
end
function cell:before (x,y)
..
end
function cell:activity (x,y)
..
end
function cell:after (x,y)
..
end
function cell:color (x,y)
return N
end
Schließlich wird das Weltmodell erzeugt.
Es muss folgende Parameter enthalten:
UPRIGHT
, DOWNLEFT
, RANDOM
, RANDOM0
),model = {
rows : N,
columns : N,
size : N,
neighbors : N,
scheduler : S,
palette : { S, S, ..},
cell : cell,
data : { .. }
}
Der Zustand einer Zelle besteht aus einer oder mehreren Variablen
I.A. ist eine Variable ein Sensor, der von der Nachbarzellen gelesen wird
I.A. ist eine andere Variable der zustandsbestimmende Aktor einer Zelle, die auch die Farbe einer Zelle bestimmt
Die Nachbarzellen sind durch das Objekt self.neighbors erreichbar (direkt mit dem Shared Memory Modell, indirekt durch get/set Methoden über Kommunikationskanäle im Distributed Memory Modell). Die Felder und relativen Positionen sind wie folgt gezeigt:
if model.neighbors == 4 then
-- Neumann
self.neighbors = {
left = {-1,0},
right = {1,0},
bottom = {0,1},
top = {0,-1}
}
elseif model.neighbors == 8 then
-- Moore
self.neighbors = {
left = {-1,0},
right = {1,0},
bottom = {0,1},
top = {0,-1},
topleft = {-1,-1},
topright = {1,-1},
bottomleft = {-1,1},
bottomright = {1,1}
}
end
┌──────────┬──────────┬──────────┬────▶ x
│ │ │ │
│ topleft │ top │ topright │
│ │ │ │
├──────────┼──────────┼──────────┤
│ │ │ │
│ left │ pixel │ right │
│ │ │ │
├──────────┼──────────┼──────────┤
│ │ │ │
│ bottom- │ bottom │ bottom- │
│ left │ │ right │
├──────────┴──────────┴──────────┘
│
│
▼ y
self.neighbors.left[key]
(Je nach Position einer Zelle in der Welt können Nachbarelemente == nil
sein → vorher testen!)self.read(from,key)
und self.write(to,key,val)
(mit Nachrichtenaustausch)Die Simulation ist in Schritte unterteilt
In einem Schritt werden je nach Scheduler die Zellen des Netzwerks der Reihe nach ausgeführt.
Wenn der Schedulername nicht mit einer "0" endet werden die Funktionen der Zellen in drei Phasen ausgeführt (jede Phase durchläuft immer alle Zellen):
Die Phasen sind Synchronisationsbarrieren (d.h., Ausführung einer Phase ist eine Forkoperation, Terminierung einer Joinoperation)!
Wenn der Schedulername mit einer "0" endet werden die drei Funktionen before, activity und after für jede Zelle einzeln zusammenhängend ausgeführt (Einphasensimulation)
▸
|
✗
≡
|
▸
|
✗
≡
|
▸
|
✗
≡
|
▸
|
✗
≡
|
Verschiedene Modelle (Pseudonotation) zum Ausprobieren.
state : {open,wasOpen}
before : wasOpen=open
acitivty :
surrounding = count(neighbors,wasopen==true)
open=(wasOpen and surrounding >= A) or surrounding >= B
initialize : open=random()>density
state : {alive,wasAlive,simulated}
before : wasAlive=alive
activity :
surrounding = count(neighbors,wasopen==true)
simulated < 20?
alive = surrounding == 1 or surrounding == 2 and alive;
simulated > 20 and surrounding == 2?
alive = true;
simulated = self.simulated+1
initialize : alive = random() > 0.6; simulated=0
state : {alive,wasAlive}
before : wasAlive=alive
activity :
surrounding = count(neighbors, wasAlive==true)
alive = surrounding == 3 or surrounding == 2 and alive
initialize : alive = random() > 0.5
UPRIGHT, DOWNLEFT, RANDOM, RANDOM0
. Beobachte und beschreibe Unterschiede in der Emergenz! Was passiert wenn man nur eine Variable open als Sensor und Aktor verwendet (ohne before Berechnung)?
▸
|
✗
≡
|
▸
|
✗
≡
|
▸
|
✗
≡
|
▸
|
✗
≡
|