package dag12import (3"fmt"4"strings"56"github.com/hashicorp/go-multierror"7)89// Reduce performs a transitive reduction on g. A transitive reduction removes10// as many edges as possible while maintaining the same "reachability" as the11// original graph: any node N reachable from node S will still be reachable12// after a reduction.13func Reduce(g *Graph) {14// A direct edge between two vertices can be removed if that same target15// vertex is indirectly reachable through another edge.16//17// To detect this, we iterate through all vertices in the graph, performing a18// depth-first search at its dependencies. If the target vertex is reachable19// from the source vertex, the edge is removed.20for source := range g.nodes {21_ = Walk(g, g.Dependencies(source), func(direct Node) error {22// Iterate over (direct, indirect) edges and remove (source, indirect)23// edges if they exist. This is a safe operation because other is still24// reachable by source via its (source, direct) edge.25for indirect := range g.outEdges[direct] {26g.RemoveEdge(Edge{From: source, To: indirect})27}28return nil29})30}31}3233// Validate checks that the graph doesn't contain cycles34func Validate(g *Graph) error {35var err error3637// Check cycles using strongly connected components algorithm38for _, cycle := range StronglyConnectedComponents(g) {39if len(cycle) > 1 {40cycleStr := make([]string, len(cycle))41for i, node := range cycle {42cycleStr[i] = node.NodeID()43}44err = multierror.Append(err, fmt.Errorf("cycle: %s", strings.Join(cycleStr, ", ")))45}46}4748// Check self references49for _, e := range g.Edges() {50if e.From == e.To {51err = multierror.Append(err, fmt.Errorf("self reference: %s", e.From.NodeID()))52}53}5455return err56}575859