1 Prof. Dr. Th. Letschert FB MNI 25. Juni 2013 Hinweise Aufgabenblatt 9 - Verteilte Systeme Aufgabe 1 Die natürlichen Zahlen mit den Operationen ggt und kgv bilden einen Verband mit der Relation | (ist Teiler von). Ein adaptierter Wellen-Algorithmus für die GGT-Berechnung könnte so aussehen: package aufgabe_1 import import import import import akka.actor.Actor akka.actor.ActorRef akka.actor.ActorSystem akka.actor.Props scala.collection.immutable.ListSet sealed abstract class Msg case class Start(neighbors: List[ActorRef], init: Int) extends Msg case class Token(msg : Int) extends Msg class var var var GGTInitiator extends Actor { neighbors : List[ActorRef] = List() nr : Int = -1 rec = 0 def receive = { case Start(n, s) => { neighbors = n nr = s for (q <- neighbors) { q ! Token(nr) } } case Token(s) => { rec = rec + 1 nr = GGT.ggt(nr, s) if (rec == neighbors.length) { println("decide: " + nr) } } } } class var var var var GGTFollower(id: Int) extends Actor { neighbors : List[ActorRef] = List() nr : Int = -1 rec = 0 father : Option[ActorRef] = None def receive = { case Start(n, s) => { neighbors = n nr = s } case Token(s) => { nr = GGT.ggt(nr, s) rec = rec + 1 if (!father.isDefined) { 2 father = Some(sender) for (q <- neighbors) yield { if (q != father.get) { q ! Token(nr) } } } if (rec == neighbors.length) { father.get ! Token(nr) } } } } object GGT extends App { def ggt(x: Int, y: Int) : Int = if (x==y) x else ggt(max(x,y) - min(x,y), min(x,y)) def max(x: Int, y: Int) : Int = if (x>y) x else y def min(x: Int, y: Int) : Int = if (x>y) y else x val system = ActorSystem("GGTSystem") val actor: List[ActorRef] = Range(0,8) .map(i => if (i == 0) system.actorOf(Props(new GGTInitiator), name = "actor_"+i) else system.actorOf(Props(new GGTFollower(i)), name = "actor_"+i)) . toList actor(1) actor(2) actor(3) actor(4) actor(5) actor(6) actor(7) actor(0) ! ! ! ! ! ! ! ! Start(List(actor(0), actor(2), actor(6)), 60) Start(List(actor(3), actor(4)), 180) Start(List(actor(2), actor(6)), 180) Start(List(actor(2), actor(5), actor(7)), 90) Start(List(actor(4)), 18) Start(List(actor(7), actor(1)), 360) Start(List(actor(4), actor(6)), 360) Start(List(actor(1)), 120) } Aufgabe 2 Handelt es sich bei der Berechnung der Routing-Tabelle in einem Netz um eine Infimum-Berechnung? Erläutern Sie! Zunächst muss definiert werden, um was es sich bei der “Berechnung der Routing-Tabelle” handelt: Eine Routingtabelle sei einfach eine Tabelle, in der zu jeder Quelle und jedem Ziel vermerkt ist, über welchen Nachbarknoten der Quelle das Ziel mit minimalen Kosten zu erreichen ist. Da diese Information sich aus einer Gesamtübersicht des Netzes ergibt, identifizieren wir einfach die Routing Tabelle mit einer Gesamtdarstellung des Netzes. Die Berechnung besteht dann einfach darin, die Informationen über das Netz zusammen zu tragen. Wir identifizieren das Netz mit seinem Graph. Sei G = V, E mit E ⊆ V × V ein Graph. Die Nachbarn eines Knotens v ∈ V seien die Knoten v 0 ∈ V mit (v, v 0 ) ∈ E. Eine Tabelle t ∈ T ⊆ E sei eine Teilmenge der Kanten E. Die gesuchte Operation ist die Mengenvereinigung von Tabellen. Diese Operation ist offensichtlich assoziativ, kommutativ und idempotent. Das Infimum (Supremum) ist den Menge E. Ein Routing–Algorithmus könnte also in einer Welle die Informationen über das Netz zusammentragen und dann die Informationen geeignet aufbereiten, etwa in dem die Information per Broadcast an alle anderen Knoten (Router) gesendet wird und diese dann jeweils mit einem Single Source Shortest Path Algorithmus (Dijkstra) die beste Route zu allen anderen berechnen. Ein Wellenalgorithmus kann die Informationen über die direkten Nachbarn sammeln: package aufgabe_2 3 import import import import import akka.actor.Actor akka.actor.ActorRef akka.actor.ActorSystem akka.actor.Props scala.collection.immutable.ListSet sealed abstract class Msg case class Start(neighbors: List[ActorRef], init: Set[Int]) extends Msg case class LSMsg(links: Set[Pair[Int, Int]]) extends Msg class var var var LSInitiator extends Actor { neighbors : List[ActorRef] = List() set : ListSet[Pair[Int, Int]] = ListSet() rec = 0 def receive = { case Start(n, s) => { neighbors = n s.foreach(n => set = set + Pair(0 , n)) for (q <- neighbors) { q ! LSMsg(set) } } case LSMsg(s) => { rec = rec + 1 set = set ++ s if (rec == neighbors.length) { println("decide: " + set) } } } } class var var var var LSFollower(id: Int) extends Actor { neighbors : List[ActorRef] = List() set : ListSet[Pair[Int, Int]] = ListSet() rec = 0 father : Option[ActorRef] = None def receive = { case Start(n, s) => { neighbors = n s.foreach(n => set = set + Pair(id , n)) } case LSMsg(s) => { set = set ++ s rec = rec + 1 if (!father.isDefined) { father = Some(sender) for (q <- neighbors) yield { if (q != father.get) { q ! LSMsg(set) } } } if (rec == neighbors.length) { father.get ! LSMsg(set) } } } } 4 object Routing extends App { val system = ActorSystem("RoutingSystem") val actor: List[ActorRef] = Range(0,8) .map(i => if (i == 0) system.actorOf(Props(new LSInitiator), name = "actor_"+i) else system.actorOf(Props(new LSFollower(i)), name = "actor_"+i)) . toList actor(1) actor(2) actor(3) actor(4) actor(5) actor(6) actor(7) actor(0) ! ! ! ! ! ! ! ! Start(List(actor(0), actor(2), actor(6)), ListSet[Int](0, 2, 6)) Start(List(actor(3), actor(4)), ListSet[Int](3, 4)) Start(List(actor(2), actor(6)), ListSet[Int](2, 6)) Start(List(actor(2), actor(5), actor(7)), ListSet[Int](2, 5, 7)) Start(List(actor(4)), ListSet[Int](4)) Start(List(actor(7), actor(1)), ListSet[Int](7, 1)) Start(List(actor(4), actor(6)), ListSet[Int](4, 6)) Start(List(actor(1)), ListSet[Int](1)) } Der Initiator kennt jetzt den Graph und kann daraus die Routingtabellen berechnen und verschicken, oder er verschickt den Graph und die Knoten berechnen daraus ihre Routing–Tabelle. Damit haben wir einen Link-State Routing-Algorithmus realisiert. Die alternative Methode nennt sich Distance Vector Routing und operiert wie folgt: Jeder Knoten x verwaltet • Eine Tabelle cx mit den Kosten cx (n) der Verbindung von x zu einem Nachbarn n aus der Menge der Nachbarn Nx . • Eine Tabelle mit den Kosten Dx (v) des Weges zu einem beliebigen Ziel v – der eigene Distanz-Vektor. • Die Distanz-Vektoren Dn (v) der Nachbarn n. Die Aktionen sind: • Jeder Knoten v sendet in periodischen Abständen seinen Distanz-Vektor Dv zu den Nachbarn. • Jeder Knoten v, der einen Distanz-Vektor Dn von einem Nachbarn n empfängt, prüft ob er eine Veränderung beinhaltet und wenn ja, dann berechnet er seinen Distanz-Vektor Dv neu Dv (z) = min{Dn (z) + cv (n) | n ∈ Nv } und versendet ihn an seine Nachbarn. In dieser Form handelt es weder um eine Infimum-Berechnung noch um einen Wellen–Algorithmus. Man könnte das Link-State Routing zu einem Wellen–Algorithmus umdeuten. Dazu müsste die gesamte Tabelle umher geschickt werden und jeder Knoten die gleiche Berechnung ausführen. Wir definieren Tabellen und eine entsprechende Operation + auf ihnen: package aufgabe_2 import scala.collection.mutable.Map case class Table(t : Map[Symbol, Map[Symbol, Pair[Symbol, Int]]] = Map()) { def addRoute(start: Symbol, to: Symbol, via: Symbol, dist: Int) { if (t.get(start).isDefined) { t(start) += (to -> (via, dist)) } else { t += (start -> Map(to -> (via, dist))) } } 5 def routeAdd(r1: Pair[Symbol, Int], r2: Pair[Symbol, Int]) : Pair[Symbol, Int] = Pair(r1._1, r1._2 + r2._2) def minRoute(p1: Pair[Symbol, Int], p2: Pair[Symbol, Int]) : Pair[Symbol, Int] = if (p1._2 < p2._2) p1 else p2 def +(that: Table) : Table = { val d1: Map[Symbol, Map[Symbol, Pair[Symbol, Int]]] = this.t val d2: Map[Symbol, Map[Symbol, Pair[Symbol, Int]]] = that match {case Table(tt) => tt} val res : Map[Symbol, Map[Symbol, Pair[Symbol, Int]]] = Map() var keys = d1.keySet ++ d2.keySet d1.keySet.foreach{ k => keys = keys ++ d1(k).keySet } d2.keySet.foreach{ k => keys = keys ++ d2(k).keySet } val infinity = 100000 // Join tables for (i <- keys) { res(i) = Map() for (j <- keys) { res(i)(j) = if (i==j) (i, 0) else if (d1.isDefinedAt(i) && d2.isDefinedAt(i)) minRoute(d1(i).getOrElse(j, (’_’, infinity)), d2(i).getOrElse(j, (’_’, infinity))) else if (d1.isDefinedAt(i) && !d2.isDefinedAt(i)) d1(i).getOrElse(j, (’_’, infinity)) else if (!d1.isDefinedAt(i) && d2.isDefinedAt(i)) d2(i).getOrElse(j, (’_’, infinity)) else (’_’, 10000) } } // Combine routes (transitve closure using the Warshall-Floyd algorithm) for (k <- keys) { for (i <- keys) { for (j <- keys) { if (i != j && k != i) { res(i)(j) = minRoute(res(i)(j), routeAdd(res(i)(k), res(k)(j))) } } } } Table(res) } } // example object Warshall extends App { val D_A : Map[Symbol, Map[Symbol, Pair[Symbol, Int]]] = Map(’A’ -> Map(’A’ -> (’A’, 0), ’B’ -> (’B’, 7), ’E’ -> (’E’, 1))) val D_B : Map[Symbol, Map[Symbol, Pair[Symbol, Int]]] = 6 Map(’B’ -> Map(’B’ -> (’B’, 0), ’A’ -> (’A’, 7), ’C’ -> (’C’, 1), ’E’ -> (’E’, 8))) val D_C : Map[Symbol, Map[Symbol, Pair[Symbol, Int]]] = Map(’C’ -> Map(’C’ -> (’C’, 0), ’B’ -> (’B’, 1), ’D’ -> (’D’, 2))) val D_D : Map[Symbol, Map[Symbol, Pair[Symbol, Int]]] = Map(’D’ -> Map(’D’ -> (’D’, 0), ’C’ -> (’C’, 2), ’E’ -> (’E’, 2))) val D_E : Map[Symbol, Map[Symbol, Pair[Symbol, Int]]] = Map(’E’ -> Map(’E’ -> (’E’, 0), ’A’ -> (’A’, 1), ’B’ -> (’B’, 8), ’D’ -> (’D’, 2))) val val val val val T_A T_B T_C T_D T_E = = = = = Table(D_A) Table(D_B) Table(D_C) Table(D_D) Table(D_E) println(T_A + T_B + T_C + T_D + T_E) } Die Operation + auf Tabellen ist kommutativ, assoziativ und idempotent: Die Berechnung des Minimums hat diese Eigenschaften. Das Minimum wird über den transitiven Abschluss der kombinierten Erreichbarkeit von zwei Graphen berechnet. Der transitive Abschluss ist kommutativ, assoziativ und idempotent. Der Algorithmus berechnet den transitiven Abschluss, weil er eine Implementierung des Warshall–Algorithmus (siehe Wikipedia) darstellt. Aufgabe 3 Implementieren und modifizieren Sie den Algorithmus von Tarry derart, dass der implizit berechnete Spannbaum ausgegeben wird. package aufgabe_3 import import import import akka.actor.Actor akka.actor.ActorRef akka.actor.ActorSystem akka.actor.Props sealed abstract class Msg case class Start(neighbors: List[ActorRef]) extends Msg case class Token(orig: Option[Node]) extends Msg case class Node(nr: Int, children: List[Node]) class var var var TarryInitiator extends Actor { neighbors : List[ActorRef] = List() unused : List[ActorRef] = List() tree : Node = Node(0, List()) def receive = { case Start(n) => { neighbors = n unused = n val q = unused(0) unused = unused.tail q ! Token(None) } case Token(x) => { if (x.isDefined) { 7 tree = Node(0, x.get :: tree.children) } if (unused.length == 0) { println("Initiator decide ! tree = " + tree) } else { val q = unused(0) unused = unused.tail q !Token(None) } } } } class var var var var TarryFollower(nr: Int) extends Actor { neighbors : List[ActorRef] = List() father : Option[ActorRef] = None tree : Node = Node(nr, List()) unused : List[ActorRef] = List() def m(i:Int) : Int = i+1 def receive = { case Start(n) => { neighbors = n unused = n } case Token(x) => { if (!father.isDefined) { father = Some(sender) } else { if (x.isDefined) { tree = Node(nr, x.get :: tree.children) } } if (unused.length != 0) { val q : Option[ActorRef] = unused.find(_ != father.get) if (q.isDefined) { unused = unused.filter(_ != q.get) q.get ! Token(None) } else { unused = unused.filter(_ != father.get) father.get ! Token(Some(tree)) } } } } } object Tarry extends App { val system = ActorSystem("TarrySystem") val actor: List[ActorRef] = Range(0,5) .map(i => if (i == 0) system.actorOf(Props(new TarryInitiator), name = "actor_"+i) else system.actorOf(Props(new TarryFollower(i)), name = "actor_"+i)) . toList actor(1) actor(2) actor(3) actor(4) actor(0) ! ! ! ! ! Start(List(actor(0), actor(2), actor(4))) Start(List(actor(1))) Start(List(actor(0), actor(4))) Start(List(actor(0), actor(1), actor(3))) Start(List(actor(4), actor(1), actor(3))) 8 } Aufgabe 4 Modifizieren und implementieren Sie den Algorithmus von Tarry zu einer Implementierung der Tiefensuche. Leicht. Aufgabe 5 Modifizieren Sie die Implementierung des Algorithmus von Tarry (oder den zur Tiefensuche) derart, dass mit seiner Hilfe die Summe der Initialwerte aller Knoten berechnet werden kann. package aufgabe_5 import import import import akka.actor.Actor akka.actor.ActorRef akka.actor.ActorSystem akka.actor.Props sealed abstract class Msg case class Start(neighbors: List[ActorRef]) extends Msg case class Token(orig: Option[Node]) extends Msg case class Node(nr: Int, children: List[Node]) object Node { def sum(n: Node) : Int = n.nr + (0 /: n.children.map(sum))(_ + _) } class var var var TarryInitiator extends Actor { neighbors : List[ActorRef] = List() unused : List[ActorRef] = List() tree : Node = Node(0, List()) def receive = { case Start(n) => { neighbors = n unused = n val q = unused(0) unused = unused.tail q ! Token(None) } case Token(x) => { //println("Actor Initiator received Token " + x + " if (x.isDefined) { tree = Node(0, x.get :: tree.children) } if (unused.length == 0) { println("Initiator decide ! tree = " + tree) println("SUM= " + Node.sum(tree)) } else { val q = unused(0) unused = unused.tail q !Token(None) } } from " + sender.path) 9 } } class var var var var TarryFollower(nr: Int) extends Actor { neighbors : List[ActorRef] = List() father : Option[ActorRef] = None tree : Node = Node(nr, List()) unused : List[ActorRef] = List() def m(i:Int) : Int = i+1 def receive = { case Start(n) => { neighbors = n unused = n } case Token(x) => { if (!father.isDefined) { father = Some(sender) } else { if (x.isDefined) { tree = Node(nr, x.get :: tree.children) } } if (unused.length == 0) { println("Follower " + nr + " decide ! unused = " + unused) } else { val q : Option[ActorRef] = unused.find(_ != father.get) if (q.isDefined) { unused = unused.filter(_ != q.get) q.get ! Token(None) } else { unused = unused.filter(_ != father.get) father.get ! Token(Some(tree)) } } } } } object Tarry extends App { val system = ActorSystem("TarrySystem") val actor: List[ActorRef] = Range(0,5) .map(i => if (i == 0) system.actorOf(Props(new TarryInitiator), name = "actor_"+i) else system.actorOf(Props(new TarryFollower(i)), name = "actor_"+i)) . toList actor(1) actor(2) actor(3) actor(4) actor(0) } ! ! ! ! ! Start(List(actor(0), actor(2), actor(4))) Start(List(actor(1))) Start(List(actor(0), actor(4))) Start(List(actor(0), actor(1), actor(3))) Start(List(actor(4), actor(1), actor(3)))