C++Guns – RoboBlog blogging the bot

08.06.2019

Graph - Basics of shortest paths

Filed under: Allgemein — Thomas @ 10:06

Shortcut links:
Basic graph definitions
Basics of shortest paths (this page)

Path lengths and distances

Sei 𝐺 = (𝑉, 𝐴) ein einfacher, gerichteter Graph und für jede Kante 𝑎 ∈ 𝐴 sei 𝑙(𝑎) eine reale Nummer und die Länge von 𝑎.

  1. Die Länge von einem gewöhnlichen Path (inklusive gewöhnliche cycles) ist die Summe von den Längen von allen Kanten in diesem Path.
  2. Abhängig vom Kontext, die Länge von einem generalized Path (inklusive generalized cycles) ist entweder definiert identisch zu gewöhnlichen Pathen, oder die Länge der backwards Kanten werden nicht addiert, sondern subtrahiert.
  3. Wenn die Länge von einem gewöhnlichen oder generalizedn Cycle negativ ist, wird dieser Cycle einen negativen cycle genannt.
  4. Für zwei Knoten 𝑠,𝑡 ∈ 𝑉, ein kürzester Path von 𝑠 nach 𝑡 ist ein (𝑠,𝑡)-path mit minimaler Länge von allen (𝑠,𝑡) Pathe.
  5. Die Distance von 𝑠 nach 𝑡 ist:
    • +unendlich wenn kein (𝑠,𝑡)-path existiert;
    • -unendlich wenn es einen negativen cycle entlang (𝑠,𝑡)-path gibt;
    • Die Länge von einem kürzesten (𝑠,𝑡)-path sonst;

Note:
In diesem Kontext sind ungerichtete Graphen als symmetrische gerichtete Graphen anzusehen, so dass zwei gegensätzlich gerichtete Kanten die selbe Länge haben.
Wenn es keine negativen cycles gibt, ist die Distanz von einem Knoten zu sich selbst 0, weil der triviale Path ohne keinen die Länge 0 hat. Auf der anderen seite, wenn ein Knoten in einem negativen cycle ist, ist seine Distanz zu sich selbst -unendlich.
Weil durch den negativen cycle ja immer ein Weg mit einer noch kürzeren Länge gefunden werden kann. Und negativ ist kleiner als Null.

Subpath property of shortest paths

Statement: Für zwei Knoten 𝑠,𝑡 ∈ 𝑉, sei 𝑝 ein kürzester (𝑠,𝑡)-Path. Seit 𝑣,𝑤 ∈ 𝑉 zwei Knoten auf dem Path 𝑝, so dass 𝑣 vor 𝑤 liegt. Dann ist der subpath von 𝑝, von 𝑣 zu 𝑤 ein kürzester (𝑣,𝑤)-Path.

Beweis: Seien 𝑝1, 𝑝2 und 𝑝3 die Subpathe von 𝑝 von 𝑠 nach 𝑣, von 𝑣 nach 𝑤 und von 𝑤 nach 𝑡. Für einen Widerspruchsbeweis angenommen, es gibt einen (𝑣,𝑤)-paht 𝑝2' der kürzer ist als 𝑝2. Dann würde die Aneinanderreihung der Pathe 𝑝1+𝑝2'+𝑝3 einen kürzeren (𝑠,𝑡)-path als 𝑝 ergeben. Da 𝑝 aber schon der kürzeste (𝑠,𝑡)-path ist, ist dies ein Widerspruch.
Die Subpathe eines kürzesten Pathes sind ebenfalls kürzeste Pathe.

Anmerkung: Gewöhnlich wird nur der Fall v=s beachtet. Also dass es nur zwei Pathe gibt und die Argumentation mit dem ersten Path gemacht wird. Diese Version wird auch prefix property genannt.

Valid distance property

Definition: Für jeden Knoten 𝑢 ∈ sei 𝑑(𝑢) eine real Zahl. Irgendeinen Zahl, muss nicht die Distanz sein. Das valid distance property wird erfüllt, wenn 𝑑(𝑤) ≤ 𝑑(𝑣) + l(𝑣,𝑤) für alle Kanten (𝑣,𝑤) ∈ 𝐴.
Das ist einfach so eine Definition.

Statement: Sei 𝑠 aus 𝑉 und für jeden Knoten 𝑢 aus 𝑉 sei d(𝑢) die Distanz von 𝑠 nach 𝑢 unter Beachtung der Kantenlängen 𝑙.
Es sei d(𝑤) ≤ d(𝑣) + 𝑙(𝑣,𝑤) Für alle Kanten (𝑣,𝑤) aus 𝐴.
Das valid distance property ist erfüllt für kürzeste Wege mit Kantenlängen im Graph.

Heißt auf gut deutsch, dass es eine "kürzeste" Kante von 𝑣 nach 𝑤 gibt und alle anderen Kanten von 𝑣 nach 𝑤 sind länger. Und da 𝑣 ja beliebig sein kann, heißt es: Es gibt einen kürzesten Weg nach 𝑤 und alle andern sind länger.

Beweis: The linke Seite der Ungleichung ist die Länge von dem kürzesten (𝑠,𝑤) Path.
Die rechte Seite ist die Länge von irgendeinem (𝑠,𝑤) Path (also die Aneinanderreihnug von dem kürzesten (𝑠,𝑣) Path und einer Kante (𝑣,𝑤))

Statement distance update: In den meisten shortest-path algorithmen, für jeden Knoten 𝑣 AUS V gibt es ein Attribut d(𝑣).
Dieses Attribut entspricht die Länge von einem (s,𝑣) Path und ist die echte Distanz von s zu 𝑣 wenn der Algorithmus terminiert.
In praktisch allen standard Algorithmen, die folgende Update Operation ist die (einzige) Operation zum updaten des d-Attributs:
1. Wähle eine Kante (𝑣,w) AUS A
2. Wenn (𝑣,w) das valid distance properts verletzt, also diese Kante einen kürzeren Weg zu w ermöglicht, also d(w) > d(𝑣) + l(𝑣,w) dann setzte d(w) := d(𝑣) + l(𝑣,w)

Distances along shortest paths

Sei 𝑠 ∈ 𝑉 und für jeden Knoten 𝑢 ∈ V sei d(𝑢) die Distanz von 𝑠 nach 𝑢 unter Berücksichtigung der Kantenlängen 𝑙.

Statement: Angenommen es gibt keine negativen Cyclen. Für eine Kante (𝑣,𝑤), es ist d(𝑤) = d(𝑣) + 𝑙(𝑣,𝑤) genau dann wenn die Kante (𝑣,𝑤) die letzte Kante von einem kürzesten (𝑠,𝑤) Path ist.

Beweis:

  1. Fall d(w) = d(v) + l(v,w)
    Dann hat die Aneinanderreihung von dem kürzesten Path von s nach v und (v,w) die Länge d(w) und damit die kürzeste Länge. (Weil die Kante (v,w) eben die letzte Kante im (s,w) Path ist.
  2. Fall d(w) != d(v) + l(v,w)
    Wegen dem valid distanz property ist d(w) < d(v) + l(v,w) (Wenn der Path nicht über die kürzeste kante geht, dann über eine Längere, also ist die Distanz auch größer als die kürzeste Distanz d(w)). Für einen Widerspruchsbeweis angenommen, dass ein kürzester (s,w) Path ist von der Form p + (v,w). Dann hat p die Länge d(w) - l(v,w) < d(v) was ein Widerspruch zur Definition von d(v) ist.

Reconstruction of a shortest path from the distances

Angenommen, dass für die Knoten 𝑠,𝑡 ∈ V die Distanz d𝑠(t) von 𝑠 to 𝑡 unter Beachtung der Kantenlängen l, welche eine reale Nummer ist (weder +inf noch -inf). Dann kann ein kürzester (𝑠,𝑡) Path 𝑝 wie folgt konstruiert werden.

  1. Initialisiere 𝑝 als leer
  2. Setzte 𝑤 := 𝑡 - Wir konstruieren den Path rückwärts von 𝑡 nach 𝑠
  3. While w /= s
    1. Finde eine Kante (𝑣,𝑤), so dass d𝑠(𝑤) = d𝑠(𝑣) + l(𝑣,𝑤)
    2. Füge die Kante (𝑣,𝑤) an den Anfang von 𝑝 an
    3. Setze 𝑤 := 𝑣

Constructing a shortest-paths arborescence or a negative cycle

Wie beim Valid distance property erwähnt, benutzen die meisten Algorithmen für das kürzeste Wege Probleme eine Standard Update Operation. Diese Operation kann wie folgt erweitert werden (mit 𝑠 für den Startknoten).
Jeder Knoten v ∈ V speichert eine von den incoming Kanten als zusätzliches Attribut:

  1. Initialisierung des Attributs:
    Dieses Attribut wird für den Knoten s mit void initialisiert.
    Für alle anderen Knoten v, die mit d(v) = +inf initialisiert wurden, wird dieses Attribut auch mit void initialisiert.

    Je nachdem ob die ausgehenden Kanten von s bei der Initialisierung beachtet, oder erst während der Iteration, ist die Distanz der abgehenden Knoten von s damit nicht +inf.
    Und das zusätzliche Attribut dieser Knoten wird mit der initial incoming Kante (s,v) initialisiert.

  2. Update: Wann immer die Standard Update Operation auf eine Kante (v,w) angewendet wird (also das Aktualisieren von der Distanz), wird diese Kante (v,w) die incoming Kante von W. Also speichert das zusätzliche Attribut immer die zuletzt gesehene Kante.

Statement:
Betrachte ein Algorithmus welcher ein kürzesten (s,v) Path für jeden Knoten v ∈ V liefert, dabei sind die die Distanzen von s eine real Nummer (weder -inf noch +inf).

Bei Terminierung gelten folgende Statements:

  1. Die incoming Kante von s ist void genau dann wenn s nicht auf einem negative cycle Paht liegt. (Das ist auch so, selbst wenn s auf einem positive cycle Path liegt. Weil der kürzeste Weg von s nach t geht nicht nochmal über s.)
  2. Die gespeicherten Kanten in dem zusätzlichen Attribut bilden ein cycle genau dann wenn die erreichten Knoten einen negativen Cycle bilden. (Einen positiven cycle bilden die Knoten ja nicht, das wissen wir bereits.)
    Jeder cycle in diesen ausgewählten Kanten ist ein negative cycle. (Weil es positive Cycle ja nicht gibt)
  3. Wenn die erreichten Knoten von s aus keinen negativen cycle enthält (Und positive cycle haben wir ja nicht), dann bilden die gewählten Kanten eine arborescene bei der der Knoten s die Wurzel ist und die arborescene spannt über alle erreichten Knoten.
    Für jeden erreichbaren Knoten v ∈ V, der Path von s zu v in dieser Arborescene ist ein kürzester (s,v)-Path. (Ja was sonst wenn man einen shortest-path Algo laufen lässt.)

(Was ist das Problem mit negativne cycle bei andern Algos? Die Terminieren nicht?)

Beweis:
Der Algorithmus liefert d(s) < 0 genau dann wenn s auf einem negativen cycle ist. (Positive cycle gibts ja nicht und sonst ist d(s) > 0). Genau in diesem Fall wird die incoming Kante von s geupdated.
Das Beweis Statement Satz 1. (Zur Erinnerung: Satz 1: Die incoming Kante von s ist void wenn NICHT negative cycle. Wenn nicht dann void.)

(Aber woher wissen wir, dass der Algo das denn auch wirklich tut?)

Für den 2. Satz beachte erst, dass der zweite Satz impliziert den only-if Teil von dem ersten Satz, so müssen wir nicht den only-if Teil von dem ersten Satz beachten.
Sei v ein erreichbarer Knoten auf einen negative cycle. Sei p ein Path auf ausgewählten Kanten welcher in v endet.
Im Besonderen, lass p eine maximale anzahl voN Kanten von allen möglichen dieser Pathe.
Dann p kann nicht an s starte, weil der algorithmus wird eine distanz Wert für v liefern, welcher kleiner ist als die echte Distanz.
Klar, p cann nicht an einem unreachable Knoten starten. Super Klar.

(Admissible/zulässige) node potentials

Für jeden Knoten 𝑣 ∈ 𝑉, sei h(𝑣) eine reale Nummer.
Für Kante 𝑎 = (𝑣,𝑤) ∈ 𝐴, sei 𝑙h(𝑎) := 𝑙(𝑎) - h(𝑣) + h(𝑤)
In diesem Kontext, diese Werte werden gewöhnliche node potential genannt.
Node potentials sind zulässig wenn 𝑙(𝑎) >= 0 für alle 𝑎 ∈ 𝐴. Also Kantengewicht(länge) positiv ist.

Statement:
Ein (𝑠,𝑡)-path ist kürzer als ein anderer (𝑠,𝑡)-path unter der Berücksichtigung der Kantenlängen 𝑙h, genau dann wenn die selbe Aussage auch wahr ist für die Kantenlängen 𝑙. Also die shortest path Algorithmen funktionieren auch weiterhin.
Im Besonderen, der kürzeste Path unter Berücksichtigung der Kantenlängen 𝑙h ist exakt der selbe kürzeste Path unter Beachtung der Kantenlängen 𝑙. Also die Algorithmen geben sogar das selbe Ergebnis zurück. Wir haben nichts kaputt gemacht. Nur verbesser.

Beweis:
Set 𝑠 = 𝑣1 - 𝑣2 - ... - 𝑣k = 𝑡 die Knoten auf einem (𝑠,𝑡)-path. Gemein ist dass s = 𝑣1 und t = 𝑣k
Die Länge auf diesem Path unter Berücksichtigung von 𝑙h ist dann *einsetzen und umformen von 𝑙h und 𝑙 *
Also: Die Länge von einem (𝑠,𝑡)-path unter Berücksichtigung von 𝑙h und 𝑙, unterscheidet sich nur durch eine Konstante h(𝑡) - h(𝑠).

Ist ja auch klar: Wenn für jede Kante h(𝑣) subtrahiert und h(𝑤) addiert wird, und bei der nächsten Kante im Path w mit dem nächsten v tauscht. Dann heben sich die Termen in dieser Summe bis auf den ersten und letzten auf.

Anmerkung:
Admissible/zulässige node potential ermöglichen effiziente Algorithmen wie Dijkstra, welche nicht-negative Kanten Längen erwarten. Durch die node potentials kann man negative Kanten Längen ins positive ziehen.

G
𝐺
V
𝑉
A
𝐴
E
𝐸
K
𝐾
a
𝑎
k
𝑘
l
𝑙
m
𝑚
n
𝑛
p
𝑝
s
𝑠
t
𝑡
u
𝑢
v
𝑣
w
𝑤
Teilemenge

phi
φ
Element von

01.06.2019

C++Guns: RE: C++ Core Guidelines: std::array und std::vector sind die erste Wahl

Filed under: Allgemein — Tags: — Thomas @ 09:06

Ich finde es schade, dass der Artikel von Rainer Grimm bei heise.de C++ Core Guidelines: std::array und std::vector sind die erste Wahl so wenig Beachtung bekommt. Er wirft nicht nur ein paar interessante Fragen auf, sondern geht auf der Kernaussage von Performance ein: Don't think, use std::vector.

Natürlich hat sich der C++ Gründervater Bjarne Stroustrup schon seit Jahren (Jahrzehnten?) mit dem Thema beschäftigt. Es lohnt sich nachzulesen in Are lists evil?

Zuallererst: Benchmarks sind super schwer und keiner kann sie richtig machen. Daher werde ich im ersten Schritt
nur versuchen die Ergebnisse auf meinem Laptop nachzuvollziehen. Und im zweiten Schritt die Datenmenge oder die
Anzahl Zugriffe erhöhen, da eine Laufzeit von 0.07 Sekunden doch sehr gering ist. Da Linux ein Multiuser/Multithread
Betriebssystem ist, passiert allerlei Nebenbei. Und diese "Störungen" versuche ich durch eine längere Messzeit
zu auszublenden.

1) Reproduzieren
1.a) Erster Versuch
Beispiel Code runter laden, compilieren, starten, "Killed".

1.b)
Ich habe nicht genug RAM oder der RAM ist zugemüllt -_- Im Beispiel Code werden 100 Millionen Integer a 4 Byte
als Nutzdaten angegeben. Das ergibt ein Speicherverbrauch von _mindestens_ 400MB. std::vector und std::deque
funktionieren noch. Bei std::list steigt das Programm aus.
Der Grund ist einfach. Während std::vector einfach 400MB am Stück allokiert, passiert bei std::list schon mehr.
Ich wage zu behaupten, dass für jedes neu eingefügte Element einmal extra Speicher auf dem Heap allokiert wird.
Damit ist der Speicher Overhead natürlich gigantisch und das Programm steigt aus.

1.c)
Ein kurzer Blick in den Systemmonitor Zeigt, dass das Programm Firefox natürlich einige GB im RAM belegt. Also das Programm
beenden und schon zeigen sich 20 Instanzen von mysql die jeweils 170MB belegen. Wozu brauche ich eine Datenbank?
Wusste gar nicht, dass so etwas installiert ist. Also mysqld Server deinstallieren und beenden.

1.d) Zweiter Versuch
Immerhin läuft std::list jetzt durch, aber bei std::forward_list bricht das Programm mit der Fehlermeldung ab:
*** Error in `./a.out': free(): invalid pointer: 0x00000000464a1320 ***
Das weitere beenden von Programmen welche viel Speicher verbrauchen führe zu einem Absturz des Systems...

1.e) Dritter Versuch
Nach einem Neustart werden "nur" 832 von 7680MB als belegt angezeigt. Diesmal hat es funktioniert!

std::vector
time: 0.0352953930
res: 4999774274

std::deque
time: 0.0827120400
res: 4999774274

std::list
time: 0.2975692830
res: 4999774274

std::forward_list
time: 0.3002444010
res: 4999774274

Damit kann ich die Werte reproduzieren. std::vector ist wie zu erwarten am schnellsten. Und std::forward_list ist langsamer als std::list.

2) Längere Laufzeit
Die Datenmenge zu erhöhen würde kein Sinn machen. Also werde ich die Summe statt nur einmal, gleich 100 mal bilden.
Die zu erwartende Laufzeit liegt damit von 4 bis 30 Sekunden.
Um einen Vergleich anzustellen wie die Laufzeiten der Container sich relativ zu std::vector verhalten:

1.000 std::ector
0.427 std::deque
0.119 std::list
0.118 std::forward_list

std::vector
time: 3.4487668950
res: 499993166900

std::deque
time: 7.2533651530
res: 499993166900

std::list
time: 28.2407733910
res: 499993166900

std::forward_list
time: 30.5225665430
res: 499993166900

Hier die neuen relativen Werte:
1.000 std::vector
0.475 std::deque
0.122 std::list
0.113 std::forward_list

Also std::forward_list ist _wirklich_ langsamer als std::list. Es bleibt ein Rätsel.

31.05.2019

Protected: Besuch von zwei Gänse

Filed under: Allgemein — Thomas @ 20:05

This content is password protected. To view it please enter your password below:

29.05.2019

Graph - Basic graph definitions

Filed under: Allgemein — Thomas @ 14:05

Shortcut links:
Basic graph definitions (this page)
Basics of shortest paths

Gerichtete und ungerichtete Graphen

  1. Ein gerichteter Graph 𝐺 = (𝑉, 𝐴) besteht aus einer endlichen Menge 𝑉 von Knoten und einem multiset 𝐴 von geordneten Paaren von Knoten. Die Elemente von 𝐴 sind die gerichteten Kanten von 𝐺.
  2. Ein ungerichteter Graph 𝐺 = (𝑉, 𝐸) besteht aus einer endlichen Menge 𝑉 von Knoten und ein multiset 𝐸 von ungeordneten Paaren von Knoten. Die Elemente von 𝐸 sind die ungerichteten Kanten von 𝐺.
  3. Ein gerichteter oder ungerichteter Graph ist simple (einfach), wenn:
    • Kein Knoten, in 𝐴 oder 𝐸, mit sich selbst verbunden ist
    • Die multisets 𝐴 oder 𝐸 sind nur sets
  4. Ein gerichteter Graph 𝐺 = (𝑉, 𝐴) ist
    • symmetrisch, wenn (𝑣,𝑤) ∈ 𝐴 impliziert (𝑤,𝑣) ∈ 𝐴
    • anti-symmetrisch, wenn (𝑣,𝑤) ∈ 𝐴 impliziert (𝑤,𝑣) ∉ 𝐴

Adjacency, incidence, degree (benachbart, verbunden, Grad)

  1. Zwei Knoten eines gerichteten oder ungerichteten Graphen heißen adjacent (benachbart), wenn sie mindestens eine gemeinsame Kante haben.
  2. Ein Knoten und eine Kante heißen incident (verbunden), wenn der Knoten zu der Kante gehört.
  3. Zwei Kanten heißen incident (verbunden), wenn sie mindestens einen gemeinsamen Knoten haben.
  4. Für einen Knoten, die Anzahl der incident (verbundenen) Kanten ist der Grad von diesem Knoten. Ein Knoten mit Grad 0 heißt isoloert.
  5. Betrachte einen gerichteten Graph 𝐺 = (𝑉, 𝐴):
    1. Für eine Kante 𝑎 = (𝑣,𝑤) ∈ 𝐴, 𝑣 ist der tail (Fuß,Startknoten) von 𝑎 und 𝑤 ist der head (Kopf/Endknoten) von 𝑎.
    2. Eine Kante 𝑎 = (𝑣,𝑤) ∈ 𝐴 ist eine outgoing (abgehende) Kante von 𝑣 und eine incoming (eingehende) Kante von 𝑤.
    3. outdegree von einem Knoten 𝑣 ∈ 𝑉 ist die Anzahl der abgehenden Kanten.
    4. indegree von einem Knoten 𝑣 ∈ 𝑉 ist die Anzahl der eingehenden Kanten.

Transpose of a graph

Für einen gerichteten Graph 𝐺 = (𝑉, 𝐴), die transpose (Vertauschung) von 𝐺 ist das Ergebnis vom Drehen aller Kanten von 𝐺. (Start- und Zielknoten tauschen)

Subgraph

  1. Gegeben sind zwei einfache, ungerichtete Graphen 𝐺1 = (𝑉1, 𝐸1) und 𝐺2 = (𝑉2, 𝐸2).
    Dann ist 𝐺1 ein subgraph von 𝐺2, wenn es eine Knotenteilmenge 𝑉' ⊆ 𝑉2 gibt und eine bijektive (umkehrbare) Funktion φ: 𝑉1 -> 𝑉' gibt, welche alle Knoten aus 𝑉1 auf die Teilmenge 𝑉' von 𝑉2 abbildet, und zwar genau so, dass eine Kante {𝑣,𝑤} aus 𝐸1 impliziert, dass es eine andere Kante {φ(𝑣),φ(𝑤)} ∈ 𝐸2 gibt.
    Denn es müssen nicht nur alle Knoten von 𝑉1 in 𝑉2 zu finden sein, sondern natürlich auch alle Kanten.
  2. Gegeben sind zwei einfache, gerichtete Graphen 𝐺1 = (𝑉1, 𝐴1) und 𝐺2 = (𝑉2, 𝐴2).
    Dann ist 𝐺1 ein subgraph von 𝐺2, wenn es eine Knotenteilmenge 𝑉' ⊆ 𝑉2 gibt und eine bijektive (umkehrbare) Funktion φ: 𝑉1 -> 𝑉' gibt, welche alle Knoten aus 𝑉1 auf die Teilmenge 𝑉' von 𝑉2 abbildet, und zwar genau so, dass eine Kante (𝑣,𝑤) aus 𝐴1 impliziert, dass es eine andere Kante (φ(𝑣),φ(𝑤)) ∈ 𝐴2 gibt.
    Quasi das selbst wie für ungerichtete Grapen.
  3. Ein spanning subgraph von einem ungerichteten oder gerichteten Graphen 𝐺 ist ein subgraph welcher alle Knoten von 𝐺 enthält.
    Aber nicht alle Kanten (es bildet sich ein Baum).

Paths

  1. Ein ordinary (gewönlicher) Path in einem ungerichteten Graphen ist eine endliche, geordnete Sequenz ({v1,v2}, {v2,v3}, ..., {vk-2, vk-1}, {vk-1,vk}) von Kanten.
  2. Ein ordinary (gewöhnlicher) Path in einem gerichteten Graphen ist eine endliche, geordnete Sequenz ((v1,v2), (v2,v3), ..., (vk-2, vk-1), (vk-1,vk)) von Kanten.
  3. Ein generalized a.k.a. weak (genereller, schwacher) Path in einem gerichteten Graphen ist eine endliche Sequenz ((v1,w1), (v2,w2), ..., (vk-1, wk-1), (vk,wk)), so dass drehen von einigen Kanten einen ordinary Path ergibt (Ein ordinary Path ist schon ein generalized Path bei dem keine Kanten gedreht werden müssen).
  4. Ein Path ist simple (einfach) wenn er keine Knoten doppelt enthält.
  5. Ein Knoten 𝑡 ∈ 𝑉 ist reachable (erreichbar) von 𝑠 ∈ 𝑉, wenn es einen Path von 𝑠 nach 𝑡 in dem Graph gibt.
    Abhängig vom Kontext, der Path muss ein ungerichteter, ein gewöhnlicher gerichteter, oder ein generalized Path sein.
    Jeder Knoten ist von sich selbst aus erreichbar, via den trivialen Path welcher nur diesen Knoten enthält und sonst keine Kanten.
  6. Die internal (internen) Knoten von einem Path jeglicher Art sind alle Knoten von diesem Path außer dem Start und Endknoten.
    (Wenn der Start oder Endknoten mehr als einmal im Path vorkommt, zählt es zu den internen Knoten)
  7. Zwei Pathe sind edge-disjoint (disjunkt) wenn sie keine gemeinsame Kante haben. Sie sind (internally) node-disjoint wenn sie keine gemeinsamen Knoten haben.

Cycles

  1. Ein (ordinaty/gewöhnlicher) cycle in einem ungerichteten Graphen ist eine endliche, geordnete Sequenz ({v1,v2}, {v2,v3}, ..., {vk-2, vk-1}, {vk-1, v1} ) von Kanten. In anderen Worten: Ein cycle ist ein Path, so dass der Startknoten gleich dem Endknoten ist.
  2. Ein (ordinaty/gewöhnlicher) cycle in einem gerichteten Graphen ist eine endliche, geordnete Sequenz ((v1,v2), (v2,v3), ..., (vk-2, vk-1), (vk-1, v1) ) von Kanten. In anderen Worten: Ein cycle ist ein Path, so dass der Startknoten gleich dem Endknoten ist.
  3. Ein generalized a.k.a. weak (genereller, schwacher) cycle in einem gerichteten Graphen ist eine endliche Sequenz ((v1,w1), (v2,w2), ..., (vk-1, wk-1), (vk,wk)), so dass drehen von einigen Kanten einen ordinary cycle ergibt (Ein ordinary cycle ist schon ein generalized cycle bei dem keine Kanten gedreht werden müssen).
  4. A cycle is simple if no proper part is a cycle of its own.
  5. Ein gerichteter oder ungerichteter Graph ist acyclic (cycle-free), wenn er kein ordinary cycle enthält. Ein gerichteten Graphen nennt man in diesem Fall DAG (directed acyclic graph).
  6. Für zwei Knoten 𝑠,𝑡 ∈ 𝑉, ein (s,t)-graph 𝐺 = (𝑉, 𝐴) ist ein DAG, so dass 𝑠 ist der einzige Knoten mit indegree 0, und 𝑡 ist der einzige Knoten mit outdegree 0.
    Ein DAG ist ein (s,t)-Graph , genau dann, wenn der Graph G die Vereinigung von (nicht notwenigerweise internally Knoten oder Kanten disjunkt) allen (s,t)-pathen ist.

Forests, trees, branchings, arborescences

  1. Ein forst ist ein cycle-free undirected Graph. Disconnected nicht vergessen sonst macht es kein Sinn.
    forest
  2. Ein tree ist ein conected forst. Also connected, cycle-free undirected Graph.
    tree
    Das ist nicht unbedingt das, was man von der Schule her noch kennt. In der Schule war ein tree immer ein directer Graph mit einem Wurzelknoten. Aber gut. Muss ja nicht.
  3. Ein branching ist ein cycle-free, directed Graph, so dass indregree für jeden Knoten 0 oder 1 ist. Das sieht gedanklich schon ehr wie ein Baum aus. Aber noch nicht ganz. Directed muss sein, klar. Sonst könnte man seine "Entscheidungen" beim "branchen" ja zurück nehmen.
  4. Eine arborescene ist ein branching, so dass genau ein Knoten indegree 0 hat. Das nennt man auch rooted trees und der einzigartige Knoten mit indegree 0 ist root. DAS ist ein Baum.
  5. Ein forest/tree/branching/arborescence in einem Graphen G ist ein Subgraph von G welcher wiederrum ein forest/tree/branching/arborescence ist.
    Naja, auf dem zweiten Blick stimmt das. Jeden Subgraph den man aus einem Tree rausholt ist wieder ein Tree.

Connectedness

Types of connectedness:

    Types of connectedness:

  1. Ein ungerichteter Graph ist connected, wenn jedes mögliche Knotenpaar durch einen Path verbunden ist.
  2. Ein ungerichteter Graph ist k-connected, wenn jedes mögliche Knotenpaar mindestens durch k internally node-disjoint Pathe verbunden ist. Einfach nur connected ist 1-connected. k=2 ist biconnected.
  3. Ein directed Graph ist weakly connected, wenn für jeden Knotenpaar ein generalized path existiert welcher diese Knoten verbindet.
  4. Ein directed Graph ist strongly connected, wenn für jedes geordnerte Knotenpaar ein ordinary path den ersten mit den zweiten Knoten verbindet.
  5. Links:

  6. Ein articulation Knoten in einem connected undirected Graph ist so ein Knoten, dass wenn der Knoten (und die angeschlossenen Kanten) entfernt wird, der Graph disconnected wird.
  7. Eine bridge in einem connected undirected Graph ist eine Kante, so dass der Graph disconnected wird wenn diese Kante entfernt wird.
  8. Connected components:

  9. Die k-connected componenten von einem ungerichteten Graphen 𝐺 = (𝑉, 𝐸) ist die inclusion-maximal Knotenmengen 𝑉' ⊆ 𝑉, so dass der Subgraph von 𝐺, hergestellt von 𝑉', k-connected ist (d.h. wenn jedes mögliche Knotenpaar in V' mindestens durch k internally node-disjoint paths miteinander verbunden sind).
    For k=1 wir sagen connected und für k=2 wir sachen biconnected compoenten von G.
  10. Die weakly und strongly connecten componenten von einem directed Graph 𝐺 = (𝑉, 𝐴) sind die inclusion-maximal Knotenmengen 𝑉' ⊆ 𝑉, so dass der Subgraph von 𝐺 induced von 𝑉' ist weak und strong connected.

Bipartite and k-partite graphs

Sei 𝐺 = (𝑉, 𝐸) ein ungerichteter Graph.

  1. Der Graph 𝐺 ist 𝑘-partite, wenn es eine 𝑘-partition {𝑉1, ..., 𝑉k} von 𝑉 gibt, so dass {𝑣,𝑤} ∉ 𝐸 ist für jedes Paar 𝑣,𝑤 ∈ 𝑉i für jedes i ∈ {1,...,𝑘}.
  2. Für 𝑘=2 wir sagen 𝐺 ist bipartite.

Statement: Ein verbundener, ungerichteter Graph ist bipartite, genau dann wenn es kein Cycle von ungerade Länge gibt.
Das lässt sich leicht erkennen, wenn man die Knoten auf dem Path abwechseln V1 und dann V2 zuordnet. Bei ungeraden Längen sind die Knoten der letzten Kante bei immer aus V1 oder V2.

Complete graphs

  1. Für eine positive ganzzahlige Nummer 𝑛, der complet graph 𝐾𝑛 = (𝑉,𝐸) ist der einzigartige undirecte Graph, so dass |𝑉| = 𝑛 und alle (ungeordneten) Paaren of Knoten in 𝐸 sind. Das heisst jeder mit jedem. Dense.
  2. Für positive ganzzahlige Nummern 𝑚 und 𝑛, der complete bipartite graph 𝐾𝑚,𝑛 = (𝑉1 UNITED 𝑉2, 𝐸) ist der einzigartige undirecte graph, so dass |𝑉1| = 𝑚 und |𝑉2| = 𝑛 und jeder (ungeordnete) Pair {𝑣,𝑤} mit 𝑣 ∈ 𝑉1 und 𝑤 ∈ 𝑉2 in 𝐸 ist.
    Also jeder von dem einem mit jeder von dem anderen.

10.05.2019

C++ Guns: play with threads and future

Filed under: Allgemein — Tags: — Thomas @ 13:05

Um schnell zu testen ob ein Rechner erreichbar ist, kann man ihn pingen. Der ping timeout dauert aber gern mal eine Sekunde. Wenn 10 Rechner nicht erreichbar sind, wartet man auch 10 Sekunden auf das Programm. Statt potentiell nur eine Sekunde. Die ping Anweisung kann man gut parallel ausführen. Zeit für std::async und std::future

Hier mein Beispiel, mit etwas Spielwiese:

#include <cstdlib>
#include <iostream>
#include <future>
#include <thread>
#include <string_view>

auto status(std::string_view host) {
  auto command = "ping -W 1 -c 1 " + std::string(host) + " >/dev/null 2>&1";
  return std::pair{std::system(command.c_str()), host};
}

auto red() {
  return "\033[32m";
}

auto green() {
  return "\033[31m";
}

auto reset() {
  return "\033[0m";
}

std::ostream& operator <<(std::ostream& s, const std::pair<int, std::string_view> p) {
  s << p.second << " ";
  if(p.first == 0) {
      s << red() << "UP " << reset();
    } else {
      s << green() << "down " << reset();
    }
  return s;
}

int main() {
    std::future f01 = std::async(std::launch::async, status, "rechner01");
    std::future f02 = std::async(std::launch::async, status, "rechner02");
    std::future f03 = std::async(std::launch::async, status, "rechner03");
    // ... 
    f01.wait();
    f02.wait();
    f03.wait();
    // ...
    std::cout << f01.get() << f02.get() << f03.get() << "\n";

}

C++ Guns: C++20 class types in non-type template parameters

Filed under: Allgemein — Tags: — Thomas @ 12:05

Mit C++20 und GCC9 wir können als Template Parameter nun auch einfach eigenen Typen angeben. Ohne uns mit Workarounds herumschlagen zu müssen.

Ein einfaches Beispiel sollte es verdeutlichen

struct MyType {
    int value;
};

template<MyType x>
auto func2() {
  return x.value;
} 

auto func3() {
    func2<MyType{1}>();
    constexpr MyType x{2};
    return func2<x>();
}

01.05.2019

ACCU 2019 Videos

Filed under: Allgemein — Tags: — Thomas @ 19:05

very nice! So Zeug hab ich bei meiner BA gebracht
Optimising a small real-world C++ application - Hubert Matthews [ACCU 2019]

This is a hands-on demonstration of optimising a small real-world application written in C++. It shows how measurement tools such as strace, perf tools, valgrind and cachegrind on Linux can be used to find the hotspots in an application. It also demonstrates some common pitfalls and how to avoid them by using different algorithms or libraries.

Contracts sind die Zukunft. Anschauen!
Programming with Contracts in C++20 - Björn Fahller [ACCU 2019]

Design by Contract is a technique to clearly express which parts of a program has which responsibilities. In the case of bugs, contracts can mercilessly point a finger at the part that violated the contract. Contracts are coming as a language feature in C++20. I will show how they can make your interfaces clearer with regards to use, but also point out pitfalls and oddities in how contracts are handled in C++. Writing good contracts can be difficult. I intend to give you guidance in how to think when formulating contracts, and how to design your interfaces such that good contracts can be written, because contracts have effects on interface design.

Genau meine Rede!
Better embedded library interfaces with modern C++ - Wouter Van Ooijen [ACCU 2019]

Traditionally, embedded applications are written in C. Using C++ is often frowned upon, because it is assumed to be less efficient. This talk shows how the interface of a typical embedded library can be made safer and more user-friendly, without being less efficient. Starting from a C-style interface modern C++ features are applied, like namespace, enum class, overloading, default argument values, std::byte, std::array, and templates.

Interessant
Audio in standard C++ - Timur Doumler [ACCU 2019]

Today, almost every computer, tablet and phone comes with audio input and output. Computer games and many other kinds of applications would be unthinkable without sound. Yet, the C++ language has no notion of it. Literature on audio in C++ is sparse. For even the simplest possible audio functionality, programmers need to deal with a confusing landscape of complex platform-specific APIs and proprietary 3rd party libraries. But audio in C++ doesn’t have to be hard!

Wow! Details von einem Profi
How C++20 Can Simplify std::tuple - Alisdair Meredith [ACCU 2019]

std::tuple has been the source of many presentations over the years, as the library specification glosses over a variety of implementation pitfalls, while successive standards increase the complexity. C++20 finally provides a number of features that, when combined, can yield a much simpler solution, saving developers of generic wrapper types from layers of expert-only code. This talk will show how applying the new Concepts language feature, in conjunction with new attributes and some extended syntax, enable the delivery of an optimized high performance implementation that is almost as simple as just declaring a class with a few data members. No metaclasses required!

Wichtig.
Implementing Physical Units Library for C++ - Mateusz Pusz [ACCU 2019]
Das ist nicht der erste Versuch eine unit library zu erstellen. Aber der erste den ich kenne der C++20 integiert. Aber keiner schafft es so richtig zu einem zufriedenstellenden Ergebnis.

This talk will present the current state of my work on designing and implementing Physical Units Library for C++. I will present all the challenges, design tradeoffs, and potential solutions to those problems. During the lecture, we will also see how new C++20 features help to make the library interface easier to use, maintain, and extend. Among others, we will see how we can benefit from class types provided as non-type template parameters, how new class template argument deduction rules simplify the interfaces, and a full power of using concepts to constrain template types.

Sehr interessanter Vortrag - auch wenn ich kein Wort verstanden habe
Allocator-Aware (AA) Software - John Lakos [ACCU 2019]
Man sollte sich vorher das hier ansehen: Local (Arena) Memory Allocators Part 1 - John Lakos - Meeting C++ 2017
und dann diesen Vortrag Local (Arena) Allocators Part II - John Lakos - Meeting C++ 2017

The performance benefits of supplying local allocators are well-known and substantial [Lakos, ACCU’17]. Still, the real-world costs associated with orchestrating the integration of allocators throughout a code base, including training, supporting tools, enlarged interfaces (and contracts), and a heightened potential for inadvertent misuse cannot be ignored. Despite substantial upfront costs, when one considers collateral benefits for clients – such as rapid prototyping of alternative allocation strategies – the case for investing in a fully allocator-aware (AA) software infrastructure (SI) becomes even more compelling. Yet there remain many “concerns” based on hearsay or specious conjecture that is either overstated or incorrect.

Etwas trocken, aber es lohnt sich das mal zu lernen
Regular Types and Why Do I Care ? - Victor Ciura [ACCU 2019]

“Regular” is not exactly a new concept (pun intended). If we reflect back on STL and its design principles, as best described by Alexander Stepanov in his 1998 “Fundamentals of Generic Programming” paper or his lecture on this topic, from 2002, we see that regular types naturally appear as necessary foundational concepts in programming. Why do we need to bother with such taxonomies ? Well, the STL now informally assumes such properties about the types it deals with and imposes such conceptual requirements for its data structures and algorithms to work properly. The new Concepts Lite proposal (hopefully part of C++20) is based on precisely defined foundational concepts such as Semiregular, Regular, EqualityComparable, DefaultConstructible, LessThanComparable (strict weak ordering), etc. Formal specification of concepts is an ongoing effort in the ISO C++ Committee and these STL library concepts requirements are being refined as part of Ranges TS proposal (experimental/ranges/concepts). Recent STL additions such as string_view, tuple, reference_wrapper, as well as new incoming types for C++20 like std::span raise new questions regarding values types, reference types and non-owning “borrow” types. Designing and implementing regular types is crucial in everyday programing, not just library design. Properly constraining types and function prototypes will result in intuitive usage; conversely, breaking subtle contracts for functions and algorithms will result in unexpected behavior for the caller. This talk will explore the relation between Regular types (and other concepts) and STL containers & algorithms with examples, common pitfalls and guidance.

Ganz nett wenn man Zeit hat
How to Teach C++ and Influence a Generation - Christopher Di Bella [ACCU 2019]

Learning to correctly use C++ is not difficult: teaching proper C++ usage is where the challenge lies, and at some point in your career, you’ll need to teach someone something about C++. You may not be a university lecturer or on-site trainer, but you could find yourself helping a colleague with their problem, presenting at a lunch-time session, or even at a conference! Perhaps you are someone who contributes to the company style guide or 'Intro to Our Repo' manual.

14.04.2019

Protected: Sandras Hochzeit

Filed under: Allgemein — Thomas @ 20:04

This content is password protected. To view it please enter your password below:

14.03.2019

C++ Guns: floating point bit round - p0476r2 Bit-casting object representations

Filed under: Allgemein — Tags: — Thomas @ 10:03

C++20

Low-level code often seeks to interpret objects of one type as another: keep the same bits, but obtain an object of a different type. Doing so correctly is error-prone: using reinterpret_cast or union runs afoul of type-aliasing rules yet these are the intuitive solutions developers mistakenly turn to.

Attuned developers use aligned_storage with memcpy, avoiding alignment pitfalls and allowing them to bit-cast non-default-constructible types.

This proposal uses appropriate concepts to prevent misuse. As the sample implementation demonstrates we could as well use static_assert or template SFINAE, but the timing of this library feature will likely coincide with concept’s standardization.

Furthermore, it is currently impossible to implement a constexpr bit-cast function, as memcpy itself isn’t constexpr. Marking the proposed function as constexpr doesn’t require or prevent memcpy from becoming constexpr, but requires compiler support. This leaves implementations free to use their own internal solution (e.g. LLVM has a bitcast opcode).

We should standardize this oft-used idiom, and avoid the pitfalls once and for all.

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0476r2.html
https://github.com/jfbastien/bit_cast/

Solange wir noch kein C++20 haben um Bit Manipulationen an Gleitkommazahlen vorzunehmen, muss memcpy herhalten.

  double value;

  // float -> integer 
  std::uint64_t n;
  std::memcpy(&n, &value, sizeof(value)); // no aliasing violation

  // Bit Manipulation an Variable 'n'
  // ...

  // integer -> float
  std::memcpy(&value, &n, sizeof(n));

06.03.2019

Profiler read/write miss Gedanken

Filed under: Allgemein — Tags: — Thomas @ 16:03

Profiler read/write miss Gedanken

write miss: unterscheiden zwischen permanenten und temporäre Daten:
Permanente Daten: wohl unausweichlich, denn die neuen Daten MÜSSEN ja vom Chache in den Hauptspeicher geschrieben werden.
Temporäre Daten: eventuell vermeidbar, wenn man sie an einer späteren Stelle im Code erstellt. Wann immer Daten in den Cache geschrieben werden, fliegen andere Daten raus die eventuell gerade erst aufwendig geladen wurden.

read miss: Unterscheiden zwischen permanent und temporären Daten:
Permanente Daten: wohl unausweichlich. wenn man über einen Datensatz iteriert der größer als der Cache ist, MÜSSEN die Daten vom Hauptspeicher in den Cache geladen werden.
Temporäre Daten: eventuell vermeidbar. Selbe Argumentation wie bei write miss.

Eine temporäre/local scope Variable wird erstellt und in den Cache geschrieben. Dabei fallen eventuell wichtige Daten aus dem Cache. Nun passieren andere Berechnungen die auch in den Cache geschrieben werden. Dabei kann die temp. Variable in den Hauptspeicher ausgelagert werden. Später muss sie wieder vom Hauptspeicher wieder in den Cache geladen werden.

Cache misses auf temporäre Daten womöglich vermeidbar. Auf permanente Daten wohl unvermeidlich. Sso kann es vorkommen, dass das Laden der Daten mehr Zeit kostet als die eigentlichen arithmetischen Berechnungen damit. Profiler die nur Zeiten anzeigen, können nicht benutzt werden um zu Entscheiden ob etwas optimiert werden kann.
Die Addressberechnung und das Laden der Daten in den Cache ist teuer. Vorallem wenn die Daten nicht hintereinander im RAM liegen.
Wenn 10 Werte aus 10 Container geladen werden, sind 10 Cache Zeilen belegt. Da immer Cachezeilen-Byte viele Daten geladen werden (64byte??)
Wenn ein struct a 10 Member geladen wird, geschieht nur Speicherzugriff an einer Stelle im RAM und es wird auch nur eine (oder wenige) Cache Zeilen belegt.
Cache Zeilen sind wertvoll. es gibt nur wenige davon (CPU abhängig).
Ganz schlimm ist es, wenn in der nächsten Iteration an ganz anderer Stelle im Array zugegriffen wird. Dann sind alle geladenen Cachezeilen ungültig. Auch bei kontinuierlicher RAM Benutzung.
(Das ist DAS Problem bei Dreiecksnetzten)
Faustformel: arithmetische Operation +-*/ kostet 1-40. Main RAM read kostet 100-500

« Newer PostsOlder Posts »

Powered by WordPress