Vicsek

Das Vicsek-Modell wurde 1995 vorgeschlagen, um das Schwarmverhalten von Vögeln oder Fischen zu modellieren. Die Idee ist, dass jedes Individuum seine Bewegungsrichtung an der seiner Nachbarn anpasst. Wenn jedes Individuum genügend Nachbarn hat und die Störeinflüsse nicht zu groß sind, bilden sich Schwärme. Videos von solchen Schwärmen werden auf allen größeren Konferenzen der Statistischen Physik gezeigt — und jetzt auch hier.

Auf GitHub findet sich das Programm, das ich für obiges Video geschrieben habe. Es ist in Rust geschrieben und zeigt die Simulation per Piston auf dem Bildschirm.

Ich habe sehr großen Gefallen an Rust gefunden — gerade für ein Projekt wie dieses scheint es ideal geeignet. Es ist so schnell wie C, aber man muss sich keinerlei Gedanken um den Speicher machen und einige andere Fehlerklassen, die der Compiler direkt verhindert. Rayon macht Parallelisierung so einfach wie OpenMP — mit dem Vorteil, dass der Compiler einen Fehler ausgibt, falls es eine Variable gibt, aus der parallel gelesen und geschrieben wird.

Als Beispiel, warum ich Rust als sehr leserlich und elegant empfinde, möchte ich folgendes (unvollständige) Beispiel ansehen.

pub enum Proximity {
    Neighbors(usize),
    Radius(f64)
}

pub struct Vicsek {
    proximity: Proximity,
}

impl Vicsek {
    fn update(bird: &mut Bird) {
        match self.proximity {
            Proximity::Neighbors(n) => self.update_direction_neighbors(bird, n, noise),
            Proximity::Radius(r) => self.update_direction_disk(bird, r, noise),
        }
    }
}

Die Methode update() passt die Richtung an, in die ihr Argument im nächsten Zeitschritt fliegen soll. In meiner Simulation gibt es zwei Möglichkeiten: entweder orientiert man sich an seinen n nächsten Nachbarn oder an allen Vögeln innerhalb eines Radius von r. Der Datentyp Proximity kann eines von beiden beinhalten — welches vorhanden ist, kann elegant per Pattern-Matching ermittelt werden.

Brauche ich länger, um Rust zu schreiben als C oder C++? Vermutlich, aber ich verbringe weniger Zeit mit dem Debuggen. Netto also mehr Spaß.

A Graph a Day

Vor einiger Zeit habe ich @randomGraphs geschrieben: Ein Twitterbot, der einen Zufallsgraphen pro Tag tweetet.

Die meisten Graphtypen, die er darstellen kann stammen aus der NetworkX Bibliothek oder sind reale Netzwerke. Ein paar Proximity Graphs habe ich selbst geschrieben. Die Darstellung und gegebenenfalls das Layout übernimmt Cytoscape oder graph-tool (dessen Autor diesem Bot folgt).

Bei diesem Projekt habe ich exzessiv Gebrauch von Pythons Decorator und Introspection gemacht, sodass man, um einen neuen Graphtyp einzuführen nur eine Methode schreiben muss, die eine Graph-Datenstruktur zurück gibt. Einstellungen, welche Darstellungen erlaubt sind, werden per decorator getätigt und alle Methoden werden per Introspection automatisch zum Pool hinzugefügt, aus dem der Zufallsgenerator zieht.

Eine typische Methode sieht etwa so aus.

@synonym("Barabasi Albert")
@synonym("preferential attachment")
@style(styles_all)
@layout(["kamada-kawai", "force-directed", "sfdp", "fruchterman_reingold", "arf", "radial_tree"])
def generateBarabasiAlbert(self, N=None, m=None, **kwargs):
    if N is None: N = random.randint(4, 400)
    if m is None: m = random.randint(1, 5)

    G = gen.barabasi_albert_graph(N, m)  # gen is networkx Generator
    details = dict(name="Barabási-Albert Graph", N=N, m=m, seed=self.seed,
                   template="{name}, N = {N}, m = {m}")

    return G, details

Und liefert für $N=226, m=1$ und das radial_tree Layout beispielsweise diesen Graph. Die Größe der Knoten wird hier von der Betweenness Centrality bestimmt.

Graph

Die @synonym Decorators ermöglichen die zweite Funktion des Bots, denn er tweetet nicht nur einmal am Tag einen zufälligen Graphen, sondern reagiert auch auf Mentions. Falls in der Mention der Name der Methode oder eines der per @synonym registrierten Worte auftaucht, antwortet er mit einem Bild des entsprechenden Graphen. Dank fuzzywuzzy ist es sogar resistent gegen Tippfehler.

Twitter unterstützt leider keine Vektorgrafiken und wandelt Bilder gerne in stark komprimierte .jpg, was gerade bei diesen Graphen zu störenden Artefakten führt. Dagegen hilft es, wenn ich einen Rand aus transparenten Pixeln dem Bild hinzufüge. Das führt dazu, dass Twitter .jpg nicht als geeignetes Format ansieht und die Bilder im verlustfreien .png ausliefert.

convert -alpha on -channel RGBA -bordercolor "rgba(0,0,0,0)" -border "1x1" input.png output.png

Graph

Der komplette Quellcode ist auf Github.

make

Als Obi-Wan zu Luke gesagt hat

This is the weapon of a Jedi Knight. Not as clumsy or random as a blaster; an elegant weapon for a more civilized age.

Obi-Wan Kenobi (1977)

Meinte er vermutlich make. (Fun Fact: make wurde auch 1977 veröffentlicht.)

Mit wenigen Zeilen im Makefile kann man nicht nur sein $\LaTeX$ Projekt kompilieren, sondern auch alle Plots neu zeichnen, die sich geändert haben. Für unser Beispiel gehen wir davon aus, dass zum Plotten Gnuplot mit dem epslatex Terminal genutzt wird und folgende Verzeichnisstruktur des Projektes vorliegt.

.
+-- data
|   + datafile1.dat
|   + datafile2.dat
+-- images
|   +-- img1.svg
|   +-- img2.tex
+-- plots
|   +-- style.gps
|   +-- plot1.gp
|   +-- plot2.gp
+-- myDocument.tex
+-- chapter1.tex
+-- chapter2.tex
+-- lit.bib

Dann kümmert sich das folgende Makefile darum, dass die Daten für die Plots heruntergeladen werden, alle Plots, TikZ und .svg parallel zu .pdf gerendert werden und sobald das geschehen ist, das Dokument kompiliert wird.

DOCUMENT = myDocument

# get all image files from their directories
PLOTS := $(wildcard plots/*.gp)
TIKZ := $(wildcard images/*.tex)
SVG := $(wildcard images/*.svg)

# we want the images to be pdf
PLOTS := $(PLOTS:%.gp=%.pdf)
SVG := $(SVG:%.svg=%.pdf)
TIKZ := $(TIKZ:%.tex=%.pdf)

IMAGES := $(PLOTS) $(SVG) $(TIKZ)

# get all tex files
TEX := $(wildcard *.tex)
BIBFILE := lit.bib

all: $(DOKUMENT).pdf

# we need chapters, images and the bib file to create our document
# also recompile, whenever one of those changes
$(DOCUMENT).pdf: $(TEX) $(IMAGES) $(BIBFILE)
$(DOCUMENT).pdf: %.pdf: %.tex
    pdflatex -interaction=batchmode $* > /dev/null
    biber $* > /dev/null
    pdflatex -interaction=batchmode $* > /dev/null
    pdflatex -interaction=batchmode $* > /dev/null

# gnuplot generates texfiles from the .gp files
# make sure to regenerate all tex files, if the style
# or the data changes
%.tex: %.gp plots/style.gps | data
    cd $(<D) && gnuplot $(<F) > /dev/null 2>&1

# use this rule to convert .svg to pdf
$(SVG): %.pdf: %.svg
    cd $(<D) && inkscape -z -A $(*F).pdf -h 1080 $(<F)

# use this rule only to generate .pdf from the "image type" .tex files
$(TIKZ) $(PLOTS): %.pdf: %.tex
    cd $(<D) && pdflatex -interaction=batchmode $(<F) > /dev/null
    rm -f $*.{log,aux} $*-inc.eps $*-inc-eps-converted-to.pdf

# rule to extract data from its archive
data: %: %.tar.xz
    tar -xf $<

# rule to download the archive with the data
%.tar.xz:
    wget -nv https://some.domain.tld/where/your/data/is/$@

clean: proper
    rm -rf data
    rm -f $(DOCUMENT).pdf

# delete temporary files
proper:
    rm -f data.tar.xz
    rm -f $(PLOTS) $(PLOTS:.pdf=.eps) *-inc.eps *-inc-eps-converted-to.pdf $(PLOTS:.pdf=.tex) plots/fit.log $(TIKZ) $(SVG)
    rm -f {$(DOCUMENT)}.{log,aux,bbl,blg,toc,out,lof,lot,snm,nav,tec,glg,glo,gls,xdy,acn,acr,alg,bcf,run.xml}

Dazu baut make einen gerichteten azyklischen Graphen (DAG) aus den Abhängigkeiten auf und führt die Dinge, deren Abhängigkeiten erfüllt sind, parallel aus.

Das grundlegende Element einer Makefile sind die Rules, die generell so aufgebaut sind

targets : prerequisites
<tab> recipe

Dabei gibt die erste Zeile die Abhängigkeiten welche prerequisites bestehen müssen, um durch Ausführung des recipe die targets zu erstellen.

Die Nützlichkeit von make wird zu großen Teilen durch automatische Variablen (zB. $*) oder Pattern Rules (%.pdf) hergestellt. Dazu verweise ich allerdings lieber auf die offizielle Dokumentation.