|
| 1 | +import SwiftyToolz |
| 2 | + |
| 3 | +public extension Graph |
| 4 | +{ |
| 5 | + /** |
| 6 | + Remove edges of the condensation graph that are not in its minimum equivalent graph |
| 7 | + |
| 8 | + Note that this will not remove any edges that are part of cycles (i.e. part of strongly connected components), as it only considers edges of the condensation graph. This is because it's [algorithmically](https://en.wikipedia.org/wiki/Feedback_arc_set#Hardness) as well as conceptually hard to decide which edges in cycles are "non-essential". We recommend dealing with cycles independently of using this function. |
| 9 | + */ |
| 10 | + mutating func removeNonEssentialEdges() |
| 11 | + { |
| 12 | + findNonEssentialEdges().forEach { self.removeEdge(with: $0) } |
| 13 | + } |
| 14 | + |
| 15 | + /** |
| 16 | + Find edges of the condensation graph that are not in its minimum equivalent graph |
| 17 | + |
| 18 | + Note that this will not find any edges that are part of cycles (i.e. part of strongly connected components), as it only considers edges of the condensation graph. This is because it's [algorithmically](https://en.wikipedia.org/wiki/Feedback_arc_set#Hardness) as well as conceptually hard to decide which edges in cycles are "non-essential". We recommend dealing with cycles independently of using this function. |
| 19 | + */ |
| 20 | + func findNonEssentialEdges() -> Set<Edge.ID> |
| 21 | + { |
| 22 | + var idsOfNonEssentialEdges = Set<Edge.ID>() |
| 23 | + |
| 24 | + // for each component graph individually ... |
| 25 | + for component in findComponents() |
| 26 | + { |
| 27 | + let componentIDs = Set(component.map({ $0.id })) |
| 28 | + let componentGraph = subGraph(nodeIDs: componentIDs) |
| 29 | + |
| 30 | + // make condensation graph |
| 31 | + let condensationGraph = componentGraph.makeCondensationGraph() |
| 32 | + |
| 33 | + // remember in which condensation node each original node is contained |
| 34 | + var condensationNodeIDByNodeID = [NodeID: StronglyConnectedComponent.ID]() |
| 35 | + |
| 36 | + for condensationNode in condensationGraph.nodes |
| 37 | + { |
| 38 | + for node in condensationNode.value.nodes |
| 39 | + { |
| 40 | + condensationNodeIDByNodeID[node.id] = condensationNode.id |
| 41 | + } |
| 42 | + } |
| 43 | + |
| 44 | + // make minimum equivalent condensation graph |
| 45 | + let minimumCondensationGraph = condensationGraph.makeMinimumEquivalentGraph() |
| 46 | + |
| 47 | + // for each original edge in the component graph ... |
| 48 | + for componentGraphEdge in componentGraph.edges |
| 49 | + { |
| 50 | + // skip this edge if it is within the same condensation node (within a strongly connected component) |
| 51 | + guard let sourceCondensationNodeID = condensationNodeIDByNodeID[componentGraphEdge.originID], |
| 52 | + let targetCondensationNodeID = condensationNodeIDByNodeID[componentGraphEdge.destinationID] |
| 53 | + else |
| 54 | + { |
| 55 | + log(error: "Nodes don't have their condensation node IDs set (but must have at this point)") |
| 56 | + continue |
| 57 | + } |
| 58 | + |
| 59 | + if sourceCondensationNodeID == targetCondensationNodeID { continue } |
| 60 | + |
| 61 | + // remove the edge if the corresponding edge in the condensation graph is not essential |
| 62 | + let essentialEdge = minimumCondensationGraph.edge(from: sourceCondensationNodeID, |
| 63 | + to: targetCondensationNodeID) |
| 64 | + |
| 65 | + if essentialEdge == nil |
| 66 | + { |
| 67 | + idsOfNonEssentialEdges += componentGraphEdge.id |
| 68 | + } |
| 69 | + } |
| 70 | + } |
| 71 | + |
| 72 | + return idsOfNonEssentialEdges |
| 73 | + } |
| 74 | +} |
0 commit comments