/// <summary> /// Attempts to break cycles in the graph by removing the deepest remove edge, as defined /// by the supplied predicate function. /// </summary> /// <param name="graph">The bidirectional graph to be processed.</param> /// <param name="isRemovable">A function indicating whether or not a particular edge can be removed (i.e. is a "soft" dependency).</param> /// <typeparam name="TVertex">The <see cref="Type" /> of the vertices of the graph.</typeparam> /// <typeparam name="TEdge">The <see cref="Type" /> of the edges of the graph.</typeparam> /// <returns>The list of edges that were removed to break the cycle(s).</returns> /// <exception cref="NonAcyclicGraphException">Occurs if one or more of the cycles present in the graph cannot be broken by removing one of its edges.</exception> public static IReadOnlyList <TEdge> BreakCycles <TVertex, TEdge>(this BidirectionalGraph <TVertex, TEdge> graph, Func <TEdge, bool> isRemovable) where TEdge : IEdge <TVertex> { var removedEdges = new List <TEdge>(); // Get cyclical dependencies found in the graph var cycles = graph.GetCycles(); // Break circular dependencies foreach (var cycle in cycles) { // Last element of Path repeats first element (so ignore duplicate) var distinctPathVertices = cycle.Path.Take(cycle.Path.Count - 1).ToArray(); var sacrificialDependency = distinctPathVertices .Select( (e, i) => { // Get the next entity in the path (circling around to the first entity on the last item) var dependentVertex = distinctPathVertices[(i + 1) % distinctPathVertices.Length]; return(new { DependentVertex = dependentVertex, CycleEdges = graph.InEdges(dependentVertex).Where(IsCycleEdge) }); }) .Reverse() .FirstOrDefault(x => x.CycleEdges.All(isRemovable)); if (sacrificialDependency == null) { graph.ValidateGraph(); // Should never get here in this situation, but throw an exception to satisfy code analysis warnings throw new NonAcyclicGraphException(); } // Remove the chosen graph edge(s) to break the cyclical dependency foreach (TEdge edge in sacrificialDependency.CycleEdges.ToArray()) { if (_logger.IsDebugEnabled) { _logger.Debug($"Edge '{edge.ToString()}' removed to prevent the following cycle: {string.Join(" --> ", cycle.Path.Select(x => x.ToString()))}"); } graph.RemoveEdge(edge); removedEdges.Add(edge); } bool IsCycleEdge(TEdge edge) => distinctPathVertices.Contains(edge.Source); } if (_logger.IsDebugEnabled) { _logger.Debug($@"The following edges were removed from the graph to prevent cycles: {string.Join(Environment.NewLine, removedEdges.Select(x => x.ToString()))}"); } return(removedEdges); }