summaryrefslogtreecommitdiffstats
path: root/lib/puppet/external/gratr/strong_components.rb
blob: 796ae16bb2e8f83612444fcf798b5905104965a2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#--
# Copyright (c) 2006 Shawn Patrick Garbett
# 
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
# 
#     * Redistributions of source code must retain the above copyright notice(s),
#       this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright notice,
#       this list of conditions and the following disclaimer in the documentation
#       and/or other materials provided with the distribution.
#     * Neither the name of the Shawn Garbett nor the names of its contributors
#       may be used to endorse or promote products derived from this software
#       without specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#++


require 'set'

module GRATR
  module Graph
    module StrongComponents
      # strong_components computes the strongly connected components
      # of a graph using Tarjan's algorithm based on DFS. See: Robert E. Tarjan
      # _Depth_First_Search_and_Linear_Graph_Algorithms_. SIAM Journal on 
      # Computing, 1(2):146-160, 1972
      #
      # The output of the algorithm is an array of components where is 
      # component is an array of vertices
      #
      # A strongly connected component of a directed graph G=(V,E) is a maximal
      # set of vertices U which is in V such that for every pair of 
      # vertices u and v in U, we have both a path from u to v 
      # and path from v to u. That is to say that u and v are reachable 
      # from each other.
      #
      def strong_components

        dfs_num    = 0
        stack = []; result = []; root = {}; comp = {}; number = {}

        # Enter vertex callback
        enter = Proc.new do |v| 
          root[v] = v
          comp[v] = :new
          number[v] = (dfs_num += 1)
          stack.push(v)
        end

        # Exit vertex callback
        exit  = Proc.new do |v|
          adjacent(v).each do |w|
            if comp[w] == :new
              root[v] = (number[root[v]] < number[root[w]] ? root[v] : root[w])
            end
          end
          if root[v] == v
            component = []
            begin
              w = stack.pop
              comp[w] = :assigned
              component << w
            end until w == v
            result << component
          end
        end

        # Execute depth first search
        dfs({:enter_vertex => enter, :exit_vertex => exit}); result

      end # strong_components
      
      # Returns a condensation graph of the strongly connected components
      # Each node is an array of nodes from the original graph
      def condensation
        sc  = strong_components
        cg  = self.class.new
        map = sc.inject({}) do |a,c| 
          c.each {|v| a[v] = c }; a
        end
        sc.each do |c|
          c.each do |v|
            adjacent(v).each {|v| cg.add_edge!(c, map[v]) unless c == map[v]}
          end
        end; cg
      end

      # Compute transitive closure of a graph. That is any node that is reachable
      # along a path is added as a directed edge.
      def transitive_closure!
        cgtc = condensation.gratr_inner_transitive_closure!
        cgtc.each do |cgv|
          cgtc.adjacent(cgv).each do |adj|
            cgv.each do |u| 
              adj.each {|v| add_edge!(u,v)}  
            end
          end
        end; self
      end

      # This returns the transitive closure of a graph. The original graph
      # is not changed.
      def transitive_closure() self.class.new(self).transitive_closure!; end

     private
      def gratr_inner_transitive_closure!  # :nodoc:
        topsort.reverse.each do |u| 
          adjacent(u).each do |v|
            adjacent(v).each {|w| add_edge!(u,w) unless edge?(u,w)}
          end
        end; self
      end
    end # StrongComponens
    
  end # Graph
end # GRATR