/// <summary> /// Sample using a random walk with teleport technique. /// </summary> /// <typeparam name="TNode"></typeparam> /// <typeparam name="TLabel"></typeparam> /// <param name="graph"></param> /// <param name="n">Upper bound on the number of nodes to sample.</param> /// <param name="p">Probability to teleport to a random node after visiting a node.</param> /// <returns></returns> public static HashSet <TNode> RandomWalkTeleport <TNode, TLabel>(this MultiDirectedGraph <TNode, TLabel> graph, int n, double p) { var V = new HashSet <TNode>(); var nodes = graph.Nodes.ToArray(); n = Math.Min(n, graph.NumNodes); // Initial teleport var v = nodes[StaticRandom.Next(nodes.Length)]; while (n > 0) { if (!V.Contains(v)) { V.Add(v); n -= 1; } double t = StaticRandom.NextDouble(); var outNeighbors = graph.Out(v).Select(eo => graph.Target(eo)).ToArray(); if (t < p || outNeighbors.Length <= 0) { // Teleport v = nodes[StaticRandom.Next(nodes.Length)]; } else { v = outNeighbors[StaticRandom.Next(outNeighbors.Length)]; } } return(V); }
/// <summary> /// Compute distances to all target nodes from a single source node. /// </summary> /// <typeparam name="TNode"></typeparam> /// <param name="graph"></param> /// <param name="source"></param> /// <returns></returns> public static Dictionary <TNode, int> SingleSourceDistances <TNode, TLabel>(MultiDirectedGraph <TNode, TLabel> graph, TNode source) { var distance = new Dictionary <TNode, int>(); foreach (var v in graph.Nodes) { distance.Add(v, int.MaxValue); } var Q = new Queue <TNode>(); var V = new HashSet <TNode>(); Q.Enqueue(source); V.Add(source); distance[source] = 0; // Breadth-first walk while (Q.Count > 0) { var s = Q.Dequeue(); foreach (var eo in graph.Out(s)) { var t = graph.Target(eo); if (!V.Contains(t)) { Q.Enqueue(t); V.Add(t); distance[t] = distance[s] + 1; } } } return(distance); }
/// <summary> /// Do a BFS sample with random seed nodes. Only use each edge label once for each outgoing edge of a node. /// </summary> /// <typeparam name="TNode"></typeparam> /// <param name="graph"></param> /// <param name="n">Upper bound on the number of nodes to sample.</param> /// <returns></returns> public static IEnumerable <TNode> DistinctLabelsSB <TNode, TLabel>(this MultiDirectedGraph <TNode, TLabel> graph, int n) { var Q = new Queue <TNode>(); var V = new HashSet <TNode>(); var nodes = graph.Nodes.ToArray(); // Possible improvement: order seed nodes by degree Utils.Shuffle(nodes); int seedIndex = 0; // Breadth-first walk while we need to add nodes while (n > 0) { if (Q.Count <= 0) { // Find next seed node, from an undiscovered connected component, and resume from there while (V.Contains(nodes[seedIndex])) { seedIndex += 1; // If we ran out of nodes early if (seedIndex == nodes.Length) { return(V); } } var seed = nodes[seedIndex]; // Add seed to queue and set of nodes Q.Enqueue(seed); V.Add(seed); n -= 1; } // Select by edge label, and target node label var u = Q.Dequeue(); var N = graph.Out(u).GroupBy(eo => Tuple.Create(graph.EdgeLabel(eo), graph.NodeLabel(graph.Target(eo)))).Select(group => graph.Target(group.First())); foreach (var v in N) { if (!V.Contains(v) && n > 0) { Q.Enqueue(v); V.Add(v); n -= 1; } } } return(V); }
/// <summary> /// Sample nodes by walking the graph. The walk is done using a generic queue. /// </summary> /// <typeparam name="TNode"></typeparam> /// <typeparam name="TLabel"></typeparam> /// <typeparam name="TQueue"></typeparam> /// <param name="graph"></param> /// <param name="n"></param> /// <returns></returns> public static HashSet <TNode> QueuedSampler <TNode, TLabel, TQueue>(this MultiDirectedGraph <TNode, TLabel> graph, int n) where TQueue : GenericQueue <TNode>, new() { var V = new HashSet <TNode>(); var Q = new TQueue(); var nodes = graph.Nodes.ToArray(); Utils.Shuffle(nodes); int seedIndex = 0; // Walk while we need to add nodes while (n > 0) { if (Q.Count <= 0) { // Find next seed node, from an undiscovered connected component, and resume from there while (V.Contains(nodes[seedIndex])) { seedIndex += 1; // If we ran out of nodes early if (seedIndex == nodes.Length) { return(V); } } var seed = nodes[seedIndex]; // Add seed to queue and set of nodes Q.Put(seed); V.Add(seed); n -= 1; } var u = Q.Take(); var N = graph.Out(u).Select(eo => graph.Target(eo)); foreach (var v in N) { if (!V.Contains(v) && n > 0) { Q.Put(v); V.Add(v); n -= 1; } } } return(V); }
private void refine() { var newPartition = new Dictionary <TNode, int>(); foreach (var u in graph.Nodes) { if (owner[u] == this) { var H = new HashSet <int>(); H.Add(Hash(graph.NodeLabel(u))); // Compute the hashes for pairs (label[u, v], pId_k-1(v)) foreach (var eo in graph.Out(u)) { var v = graph.Target(eo); var edgeHash = Hash(graph.EdgeLabel(eo), partition[v]); if (!H.Contains(edgeHash)) { H.Add(edgeHash); } } // Combine the hashes using some associative-commutative operator int hash = 0; foreach (var edgeHash in H) { hash += edgeHash; } // Assign partition block to node newPartition.Add(u, hash); // Determine if u has changed changed[u] = partition[u] != hash; } else { newPartition.Add(u, partition[u]); } } partition = newPartition; }
/// <summary> /// Collapse a graph which contains parallel edges into a graph without parallel edges. /// Node labels are all set to 1. /// Edge labels indicate how many parallel edges there originally were from the source to the target. /// </summary> /// <typeparam name="TNode">Type of node.</typeparam> /// <typeparam name="TLabel">Type of label.</typeparam> /// <param name="graph">Graph to collapse.</param> /// <returns>A copy of the original graph with node labels set to 1 and edge labels indicating how often that edge occurred in the original graph.</returns> public static MultiDirectedGraph <TNode, int> Collapse <TNode, TLabel>(MultiDirectedGraph <TNode, TLabel> graph) { var edgeWeights = new Dictionary <Tuple <TNode, TNode>, int>(); var transformed = new MultiDirectedGraph <TNode, int>(); // Copy nodes foreach (var u in graph.Nodes) { transformed.AddNode(u, 1); } // Count edges from u to v foreach (var u in graph.Nodes) { foreach (var eo in graph.Out(u)) { var v = graph.Target(eo); var t = Tuple.Create(u, v); if (!edgeWeights.ContainsKey(t)) { edgeWeights.Add(t, 0); } edgeWeights[t] += 1; } } // Add weighted edges to the transformed graph foreach (var kvp in edgeWeights) { var u = kvp.Key.Item1; var v = kvp.Key.Item2; var w = kvp.Value; transformed.AddEdge(u, v, w); } return(transformed); }
/// <summary> /// Computes a reduced graph under some bisimulation equivalence relation. /// The input graph must be partitioned by the partitioner modulo said equivalence relation. /// </summary> /// <typeparam name="TNode">Node type.</typeparam> /// <typeparam name="TLabel">Label type.</typeparam> /// <param name="graph">Input graph.</param> /// <param name="labels">Labels of graph.</param> /// <param name="partitioner">Function which partitions the graph modulo some bisimulation equivalence relation.</param> /// <returns>A reduced graph where each partition block is a node and edges are reconstructued such that bisimulation equivalence is maintained.</returns> public static MultiDirectedGraph <int, TLabel> ReducedGraph <TNode, TLabel>(MultiDirectedGraph <TNode, TLabel> graph, Func <IDictionary <TNode, int> > partitioner) { var reduced = new MultiDirectedGraph <int, TLabel>(); var partition = partitioner(); var inverted = Utils.Invert(partition); // Add a node for each partition block foreach (var kvp in inverted) { var block = kvp.Key; var nodes = kvp.Value.ToArray(); // var someNode = Utils.Shuffled(nodes).First(); var someNode = nodes.First(); reduced.AddNode(block, graph.NodeLabel(someNode)); } // Add the edge going from each partition block to another foreach (var kvp in inverted) { var block = kvp.Key; var nodes = kvp.Value.ToArray(); // var someSource = Utils.Shuffled(nodes).First(); var someSource = nodes.First(); foreach (var eo in graph.Out(someSource)) { var someTarget = graph.Target(eo); var label = graph.EdgeLabel(eo); if (!reduced.HasEdge(block, partition[someTarget], label)) { reduced.AddEdge(block, partition[someTarget], label); } } } return(reduced); }
private void refine() { var newPartition = new Dictionary <TNode, int>(); partitionMap.Clear(); int counter = 0; foreach (var u in graph.Nodes) { if (owner[u] == this) { var S = new HashSet <Tuple <TLabel, int> >(); foreach (var eo in graph.Out(u)) { var v = graph.Target(eo); var t = Tuple.Create(graph.EdgeLabel(eo), partition[v]); if (!S.Contains(t)) { S.Add(t); } } var signature = Tuple.Create(graph.NodeLabel(u), S); if (!partitionMap.ContainsKey(signature)) { partitionMap.Add(signature, counter++); } // Assign partition block to node newPartition.Add(u, partitionMap[signature]); } } partition = newPartition; }
/// <summary> /// Estimate the (unbounded) bisimulation partition of a graph. /// Uses a hash function to determine partition block signature equivalence. /// </summary> /// <returns></returns> public IDictionary <TNode, int> EstimateBisimulationReduction() { // List of partitions; the k+1-th entry in the list corresponds to the k-th bisimulation partition var partitions = new List <Dictionary <TNode, int> >(); stopwatch.Reset(); stopwatch.Start(); { // Base (k = 0); add empty partition partitions.Add(new Dictionary <TNode, int>()); // Assign block to each node; depending on the node's label foreach (var node in graph.Nodes) { var label = graph.NodeLabel(node); partitions[0].Add(node, Utils.Hash(label)); } // Step (k > 0); repeat until the previous partition is no longer refined int k = 0; do { // Add empty partition k += 1; partitions.Add(new Dictionary <TNode, int>()); foreach (var u in graph.Nodes) { var H = new HashSet <int>(); H.Add(Utils.Hash(graph.NodeLabel(u))); // Compute the hashes for pairs (label[u, v], pId_k-1(v)) foreach (var eo in graph.Out(u)) { var v = graph.Target(eo); var edgeHash = Utils.Hash(graph.EdgeLabel(eo), partitions[k - 1][v]); if (!H.Contains(edgeHash)) { H.Add(edgeHash); } } // Combine the hashes using some associative-commutative operator int hash = 0; foreach (var edgeHash in H) { hash += edgeHash; } // Assign partition block to node partitions[k].Add(u, hash); } } while (partitions[k].Values.Distinct().Count() > partitions[k - 1].Values.Distinct().Count()); // Remove last partition because it is equivalent to the second last partition partitions.RemoveAt(partitions.Count - 1); } stopwatch.Stop(); return(partitions[partitions.Count - 1]); }
/// <summary> /// KerninghanLin algorithm which refines a partition. /// Equalizes block sizes and swaps nodes between partition blocks to minimize the edge cut. /// </summary> /// <typeparam name="TNode">Type of node.</typeparam> /// <typeparam name="TLabel">Type of label.</typeparam> /// <param name="graph">The graph of the partition.</param> /// <param name="partition">The partition to refine.</param> /// <param name="K">Maximum number of iterations.</param> public static void KernighanLin <TNode, TLabel>(MultiDirectedGraph <TNode, TLabel> graph, Dictionary <TNode, int> partition, int K) { // Compute ED and ID of each node var ED = new Dictionary <TNode, int>(); var ID = new Dictionary <TNode, int>(); Func <TNode, int> D = node => ED[node] - ID[node]; foreach (var node in graph.Nodes) { ED.Add(node, 0); ID.Add(node, 0); } foreach (var edge in graph.Edges) { var s = graph.Source(edge); var t = graph.Target(edge); if (partition[s] == partition[t]) { ID[s] += 1; ID[t] += 1; } else { ED[s] += 1; ED[t] += 1; } } // Stick nodes into sets of their partition block var inverted = Utils.Invert(partition); var AI = inverted.Keys.First(); var BI = inverted.Keys.Last(); // Swaps a node from its original partition block to the other Action <TNode> swap = node => { foreach (var edge in graph.Out(node).Concat(graph.In(node))) { var neighbor = graph.Target(edge); if (node.Equals(neighbor)) { continue; } if (partition[node] == partition[neighbor]) { // Will be in other block now ID[neighbor] -= 1; ID[node] -= 1; ED[neighbor] += 1; ED[node] += 1; } else { // Will be in same block now ID[neighbor] += 1; ID[node] += 1; ED[neighbor] -= 1; ED[node] -= 1; } } if (partition[node] == AI) { partition[node] = BI; inverted[AI].Remove(node); inverted[BI].Add(node); } else { partition[node] = AI; inverted[AI].Add(node); inverted[BI].Remove(node); } }; // Equalize block sizes while (Math.Abs(inverted[AI].Count - inverted[BI].Count) > 1) { if (inverted[AI].Count > inverted[BI].Count) { // Move from A to B var a = inverted[AI].MaxBy(node => D(node)); swap(a); } else { // Move from B to A var b = inverted[BI].MaxBy(node => D(node)); swap(b); } } // Keep performing positive swaps int n = Math.Min(inverted[AI].Count, inverted[BI].Count); for (int i = 0; i < K; i++) { bool hasGained = false; var AA = new HashSet <TNode>(inverted[AI]); var BB = new HashSet <TNode>(inverted[BI]); for (int j = 0; j < n; j++) { var a = AA.MaxBy(node => D(node)); var b = BB.MaxBy(node => D(node)); int gain = D(a) + D(b); if (graph.HasEdge(a, b)) { gain -= 2; } if (graph.HasEdge(b, a)) { gain -= 2; } if (gain > 0) { hasGained = true; swap(a); swap(b); } AA.Remove(a); BB.Remove(b); } if (!hasGained) { break; } } }