plexus 0.5.4 → 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/Gemfile +3 -0
  2. data/LICENSE +37 -0
  3. data/README.md +208 -0
  4. data/Rakefile +25 -0
  5. data/lib/plexus.rb +90 -0
  6. data/lib/plexus/adjacency_graph.rb +225 -0
  7. data/lib/plexus/arc.rb +60 -0
  8. data/lib/plexus/arc_number.rb +50 -0
  9. data/lib/plexus/biconnected.rb +84 -0
  10. data/lib/plexus/chinese_postman.rb +91 -0
  11. data/lib/plexus/classes/graph_classes.rb +28 -0
  12. data/lib/plexus/common.rb +63 -0
  13. data/lib/plexus/comparability.rb +63 -0
  14. data/lib/plexus/directed_graph.rb +78 -0
  15. data/lib/plexus/directed_graph/algorithms.rb +95 -0
  16. data/lib/plexus/directed_graph/distance.rb +167 -0
  17. data/lib/plexus/dot.rb +94 -0
  18. data/lib/plexus/edge.rb +38 -0
  19. data/lib/plexus/ext.rb +79 -0
  20. data/lib/plexus/graph.rb +628 -0
  21. data/lib/plexus/graph_api.rb +35 -0
  22. data/lib/plexus/labels.rb +112 -0
  23. data/lib/plexus/maximum_flow.rb +77 -0
  24. data/lib/plexus/ruby_compatibility.rb +17 -0
  25. data/lib/plexus/search.rb +510 -0
  26. data/lib/plexus/strong_components.rb +93 -0
  27. data/lib/plexus/support/support.rb +9 -0
  28. data/lib/plexus/undirected_graph.rb +56 -0
  29. data/lib/plexus/undirected_graph/algorithms.rb +90 -0
  30. data/lib/plexus/version.rb +6 -0
  31. data/spec/biconnected_spec.rb +27 -0
  32. data/spec/chinese_postman_spec.rb +27 -0
  33. data/spec/community_spec.rb +44 -0
  34. data/spec/complement_spec.rb +27 -0
  35. data/spec/digraph_distance_spec.rb +121 -0
  36. data/spec/digraph_spec.rb +339 -0
  37. data/spec/dot_spec.rb +48 -0
  38. data/spec/edge_spec.rb +158 -0
  39. data/spec/inspection_spec.rb +38 -0
  40. data/spec/multi_edge_spec.rb +32 -0
  41. data/spec/neighborhood_spec.rb +36 -0
  42. data/spec/properties_spec.rb +146 -0
  43. data/spec/search_spec.rb +227 -0
  44. data/spec/spec.opts +4 -0
  45. data/spec/spec_helper.rb +59 -0
  46. data/spec/strong_components_spec.rb +61 -0
  47. data/spec/triangulated_spec.rb +125 -0
  48. data/spec/undirected_graph_spec.rb +220 -0
  49. data/vendor/priority-queue/CHANGELOG +33 -0
  50. data/vendor/priority-queue/Makefile +140 -0
  51. data/vendor/priority-queue/README +133 -0
  52. data/vendor/priority-queue/benchmark/dijkstra.rb +171 -0
  53. data/vendor/priority-queue/compare_comments.rb +49 -0
  54. data/vendor/priority-queue/doc/c-vs-rb.png +0 -0
  55. data/vendor/priority-queue/doc/compare_big.gp +14 -0
  56. data/vendor/priority-queue/doc/compare_big.png +0 -0
  57. data/vendor/priority-queue/doc/compare_small.gp +15 -0
  58. data/vendor/priority-queue/doc/compare_small.png +0 -0
  59. data/vendor/priority-queue/doc/results.csv +37 -0
  60. data/vendor/priority-queue/ext/priority_queue/CPriorityQueue/extconf.rb +2 -0
  61. data/vendor/priority-queue/ext/priority_queue/CPriorityQueue/priority_queue.c +947 -0
  62. data/vendor/priority-queue/lib/priority_queue.rb +14 -0
  63. data/vendor/priority-queue/lib/priority_queue/c_priority_queue.rb +1 -0
  64. data/vendor/priority-queue/lib/priority_queue/poor_priority_queue.rb +46 -0
  65. data/vendor/priority-queue/lib/priority_queue/ruby_priority_queue.rb +526 -0
  66. data/vendor/priority-queue/priority_queue.so +0 -0
  67. data/vendor/priority-queue/setup.rb +1551 -0
  68. data/vendor/priority-queue/test/priority_queue_test.rb +371 -0
  69. data/vendor/rdot.rb +360 -0
  70. metadata +100 -10
@@ -0,0 +1,95 @@
1
+ module Plexus
2
+ # Digraph is a directed graph which is a finite set of vertices
3
+ # and a finite set of edges connecting vertices. It cannot contain parallel
4
+ # edges going from the same source vertex to the same target. It also
5
+ # cannot contain loops, i.e. edges that go have the same vertex for source
6
+ # and target.
7
+ #
8
+ # DirectedPseudoGraph is a class that allows for parallel edges, and
9
+ # DirectedMultiGraph is a class that allows for parallel edges and loops
10
+ # as well.
11
+ module DirectedGraphBuilder
12
+ module Algorithms
13
+ include Search
14
+ include StrongComponents
15
+ include Distance
16
+ include ChinesePostman
17
+
18
+ # A directed graph is directed by definition.
19
+ #
20
+ # @return [Boolean] always true
21
+ #
22
+ def directed?
23
+ true
24
+ end
25
+
26
+ # A digraph uses the Arc class for edges.
27
+ #
28
+ # @return [Plexus::MultiArc, Plexus::Arc] `Plexus::MultiArc` if the graph allows for parallel edges,
29
+ # `Plexus::Arc` otherwise.
30
+ #
31
+ def edge_class
32
+ @parallel_edges ? Plexus::MultiArc : Plexus::Arc
33
+ end
34
+
35
+ # Reverse all edges in a graph.
36
+ #
37
+ # @return [DirectedGraph] a copy of the receiver for which the direction of edges has
38
+ # been inverted.
39
+ #
40
+ def reversal
41
+ result = self.class.new
42
+ edges.inject(result) { |a,e| a << e.reverse}
43
+ vertices.each { |v| result.add_vertex!(v) unless result.vertex?(v) }
44
+ result
45
+ end
46
+
47
+ # Check whether the graph is oriented or not.
48
+ #
49
+ # @return [Boolean]
50
+ #
51
+ def oriented?
52
+ e = edges
53
+ re = e.map { |x| x.reverse}
54
+ not e.any? { |x| re.include?(x)}
55
+ end
56
+
57
+ # Balanced is the state when the out edges count is equal to the in edges count.
58
+ #
59
+ # @return [Boolean]
60
+ #
61
+ def balanced?(v)
62
+ out_degree(v) == in_degree(v)
63
+ end
64
+
65
+ # Returns out_degree(v) - in_degree(v).
66
+ #
67
+ def delta(v)
68
+ out_degree(v) - in_degree(v)
69
+ end
70
+
71
+ def community(node, direction)
72
+ nodes, stack = {}, adjacent(node, :direction => direction)
73
+ while n = stack.pop
74
+ unless nodes[n.object_id] || node == n
75
+ nodes[n.object_id] = n
76
+ stack += adjacent(n, :direction => direction)
77
+ end
78
+ end
79
+ nodes.values
80
+ end
81
+
82
+ def descendants(node)
83
+ community(node, :out)
84
+ end
85
+
86
+ def ancestors(node)
87
+ community(node, :in)
88
+ end
89
+
90
+ def family(node)
91
+ community(node, :all)
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,167 @@
1
+ module Plexus
2
+ module DirectedGraphBuilder
3
+
4
+ # This module provides algorithms computing distance between
5
+ # vertices.
6
+ module Distance
7
+
8
+ # Shortest path computation.
9
+ #
10
+ # From: Jorgen Band-Jensen and Gregory Gutin,
11
+ # [*Digraphs: Theory, Algorithms and Applications*](http://www.springer.com/mathematics/numbers/book/978-1-84800-997-4), pg. 53-54.
12
+ # Complexity `O(n+m)`.
13
+ #
14
+ # Requires the graph to be acyclic. If the graph is not acyclic,
15
+ # then see {Distance#dijkstras_algorithm} or {Distance#bellman_ford_moore}
16
+ # for possible solutions.
17
+ #
18
+ # @param [vertex] start the starting vertex
19
+ # @param [Proc, #[]] weight can be a `Proc`, or anything else accessed using the `[]`
20
+ # operator. If not a `Proc`, and if no label accessible through `[]`, it will
21
+ # default to using the value stored in the label for the {Arc}. If a `Proc`, it will
22
+ # pass the edge to the proc and use the resulting value.
23
+ # @param [Integer] zero used for math systems with a different definition of zero
24
+ #
25
+ # @return [Hash] a hash with the key being a vertex and the value being the
26
+ # distance. A missing vertex from the hash is equivalent to an infinite distance.
27
+ def shortest_path(start, weight = nil, zero = 0)
28
+ dist = { start => zero }
29
+ path = {}
30
+ topsort(start) do |vi|
31
+ next if vi == start
32
+ dist[vi], path[vi] = adjacent(vi, :direction => :in).map do |vj|
33
+ [dist[vj] + cost(vj,vi,weight), vj]
34
+ end.min { |a,b| a[0] <=> b[0]}
35
+ end;
36
+ dist.keys.size == vertices.size ? [dist, path] : nil
37
+ end
38
+
39
+ # Finds the distance from a given vertex in a weighted digraph
40
+ # to the rest of the vertices, provided all the weights of arcs
41
+ # are non-negative.
42
+ #
43
+ # If negative arcs exist in the graph, two basic options exist:
44
+ #
45
+ # * modify all weights to be positive using an offset (temporary at least)
46
+ # * use the {Distance#bellman_ford_moore} algorithm.
47
+ #
48
+ # Also, if the graph is acyclic, use the {Distance#shortest_path algorithm}.
49
+ #
50
+ # From: Jorgen Band-Jensen and Gregory Gutin,
51
+ # [*Digraphs: Theory, Algorithms and Applications*](http://www.springer.com/mathematics/numbers/book/978-1-84800-997-4), pg. 53-54.
52
+ #
53
+ # Complexity `O(n*log(n) + m)`.
54
+ #
55
+ # @param [vertex] s
56
+ # @param [Proc, #[]] weight can be a `Proc`, or anything else accessed using the `[]`
57
+ # operator. If not a `Proc`, and if no label accessible through `[]`, it will
58
+ # default to using the value stored in the label for the {Arc}. If a `Proc`, it will
59
+ # pass the edge to the proc and use the resulting value.
60
+ # @param [Integer] zero used for math systems with a different definition of zero
61
+ # @return [Hash] a hash with the key being a vertex and the value being the
62
+ # distance. A missing vertex from the hash is equivalent to an infinite distance.
63
+ def dijkstras_algorithm(s, weight = nil, zero = 0)
64
+ q = vertices; distance = { s => zero }
65
+ path = {}
66
+ while not q.empty?
67
+ v = (q & distance.keys).inject(nil) { |a,k| (!a.nil?) && (distance[a] < distance[k]) ? a : k}
68
+ q.delete(v)
69
+ (q & adjacent(v)).each do |u|
70
+ c = cost(v, u, weight)
71
+ if distance[u].nil? or distance[u] > (c + distance[v])
72
+ distance[u] = c + distance[v]
73
+ path[u] = v
74
+ end
75
+ end
76
+ end
77
+ [distance, path]
78
+ end
79
+
80
+ # Finds the distances from a given vertex in a weighted digraph
81
+ # to the rest of the vertices, provided the graph has no negative cycle.
82
+ #
83
+ # If no negative weights exist, then {Distance#dijkstras_algorithm} is more
84
+ # efficient in time and space. Also, if the graph is acyclic, use the
85
+ # {Distance#shortest_path} algorithm.
86
+ #
87
+ # From: Jorgen Band-Jensen and Gregory Gutin,
88
+ # [*Digraphs: Theory, Algorithms and Applications*](http://www.springer.com/mathematics/numbers/book/978-1-84800-997-4), pg. 56-58..
89
+ #
90
+ # Complexity `O(nm)`.
91
+ #
92
+ # @param [vertex] s
93
+ # @param [Proc, #[]] weight can be a `Proc`, or anything else accessed using the `[]`
94
+ # operator. If not a `Proc`, and if no label accessible through `[]`, it will
95
+ # default to using the value stored in the label for the {Arc}. If a `Proc`, it will
96
+ # pass the edge to the proc and use the resulting value.
97
+ # @param [Integer] zero used for math systems with a different definition of zero
98
+ # @return [Hash] a hash with the key being a vertex and the value being the
99
+ # distance. A missing vertex from the hash is equivalent to an infinite distance.
100
+ def bellman_ford_moore(start, weight = nil, zero = 0)
101
+ distance = { start => zero }
102
+ path = {}
103
+ 2.upto(vertices.size) do
104
+ edges.each do |e|
105
+ u, v = e[0], e[1]
106
+ unless distance[u].nil?
107
+ c = cost(u, v, weight) + distance[u]
108
+ if distance[v].nil? or c < distance[v]
109
+ distance[v] = c
110
+ path[v] = u
111
+ end
112
+ end
113
+ end
114
+ end
115
+ [distance, path]
116
+ end
117
+
118
+ # Uses the Floyd-Warshall algorithm to efficiently find
119
+ # and record shortest paths while establishing at the same time
120
+ # the costs for all vertices in a graph.
121
+ #
122
+ # See S.Skiena, *The Algorithm Design Manual*, Springer Verlag, 1998 for more details.
123
+ #
124
+ # O(n^3) complexity in time.
125
+ #
126
+ # @param [Proc, nil] weight specifies how an edge weight is determined.
127
+ # If it's a `Proc`, the {Arc} is passed to it; if it's `nil`, it will just use
128
+ # the value in the label for the Arc; otherwise the weight is
129
+ # determined by applying the `[]` operator to the value in the
130
+ # label for the {Arc}.
131
+ # @param [Integer] zero defines the zero value in the math system used.
132
+ # This allows for no assumptions to be made about the math system and
133
+ # fully functional duck typing.
134
+ # @return [Array(matrice, matrice, Hash)] a pair of matrices and a hash of delta values.
135
+ # The matrices will be indexed by two vertices and are implemented as a Hash of Hashes.
136
+ # The first matrix is the cost, the second matrix is the shortest path spanning tree.
137
+ # The delta (difference of number of in-edges and out-edges) is indexed by vertex.
138
+ def floyd_warshall(weight = nil, zero = 0)
139
+ c = Hash.new { |h,k| h[k] = Hash.new }
140
+ path = Hash.new { |h,k| h[k] = Hash.new }
141
+ delta = Hash.new { |h,k| h[k] = 0 }
142
+ edges.each do |e|
143
+ delta[e.source] += 1
144
+ delta[e.target] -= 1
145
+ path[e.source][e.target] = e.target
146
+ c[e.source][e.target] = cost(e, weight)
147
+ end
148
+ vertices.each do |k|
149
+ vertices.each do |i|
150
+ if c[i][k]
151
+ vertices.each do |j|
152
+ if c[k][j] &&
153
+ (c[i][j].nil? or c[i][j] > (c[i][k] + c[k][j]))
154
+ path[i][j] = path[i][k]
155
+ c[i][j] = c[i][k] + c[k][j]
156
+ return nil if i == j and c[i][j] < zero
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
162
+ [c, path, delta]
163
+ end
164
+
165
+ end # Distance
166
+ end # DirectedGraph
167
+ end # Plexus
@@ -0,0 +1,94 @@
1
+ module Plexus
2
+ module Dot
3
+
4
+ #FIXME: don't really understood where we stand with the dot generators.
5
+ # RDoc ships with a dot.rb which seems pretty efficient.
6
+ # Are these helpers still needed, and if not, how should we replace them?
7
+
8
+ # Creates a DOT::DOTDigraph for directed graphs or a DOT::DOTSubgraph for
9
+ # undirected graphs.
10
+ #
11
+ # @param [Hash] params can contain any graph property specified in
12
+ # rdot.rb. If an edge or vertex label is a kind of Hash, then the keys
13
+ # which match dot properties will be used as well.
14
+ # @return [DOT::DOTDigraph, DOT::DOTSubgraph]
15
+ def to_dot_graph(params = {})
16
+ params['name'] ||= self.class.name.gsub(/:/, '_')
17
+ fontsize = params['fontsize'] ? params['fontsize'] : '8'
18
+ graph = (directed? ? DOT::DOTDigraph : DOT::DOTSubgraph).new(params)
19
+ edge_klass = directed? ? DOT::DOTDirectedArc : DOT::DOTArc
20
+
21
+ vertices.each do |v|
22
+ name = v.to_s || v.__id__.to_s
23
+ name = name.dup.gsub(/"/, "'")
24
+
25
+ params = { 'name' => '"'+ name +'"',
26
+ 'fontsize' => fontsize,
27
+ 'label' => name}
28
+
29
+ v_label = vertex_label(v)
30
+ params.merge!(v_label) if v_label and v_label.kind_of? Hash
31
+
32
+ graph << DOT::DOTNode.new(params)
33
+ end
34
+
35
+ edges.each do |e|
36
+ if e.source.to_s.nil?
37
+ source_label = e.source.__id__.to_s
38
+ else
39
+ source_label = e.source.to_s.dup
40
+ end
41
+
42
+ if e.target.to_s.nil?
43
+ target_label = e.target.__id__.to_s
44
+ else
45
+ target_label = e.target.to_s.dup
46
+ end
47
+
48
+ source_label.gsub!(/"/, "'")
49
+ target_label.gsub!(/"/, "'")
50
+
51
+ params = { 'from' => '"'+ source_label + '"',
52
+ 'to' => '"'+ target_label + '"',
53
+ 'fontsize' => fontsize }
54
+
55
+ e_label = edge_label(e)
56
+ params.merge!(e_label) if e_label and e_label.kind_of? Hash
57
+
58
+ graph << edge_klass.new(params)
59
+ end
60
+
61
+ graph
62
+ end
63
+
64
+ # Output the dot format as a string
65
+ def to_dot(params = {})
66
+ to_dot_graph(params).to_s
67
+ end
68
+
69
+ # Call +dotty+ for the graph which is written to the file 'graph.dot'
70
+ # in the # current directory.
71
+ def dotty(params = {}, dotfile = 'graph.dot')
72
+ File.open(dotfile, 'w') {|f| f << to_dot(params) }
73
+ system('dotty', dotfile)
74
+ end
75
+
76
+ # Use +dot+ to create a graphical representation of the graph. Returns the
77
+ # filename of the graphics file.
78
+ def write_to_graphic_file(fmt = 'png', dotfile = 'graph')
79
+ src = dotfile + '.dot'
80
+ dot = dotfile + '.' + fmt
81
+
82
+ # DOT::DOTSubgraph creates subgraphs, but that's broken.
83
+ buffer = self.to_dot
84
+ buffer.gsub!(/^subgraph/, "graph")
85
+
86
+ File.open(src, 'w') {|f| f << buffer << "\n"}
87
+ system( "dot -T#{fmt} #{src} -o #{dot}" )
88
+
89
+ dot
90
+ end
91
+ alias as_dot_graphic write_to_graphic_file
92
+
93
+ end # Dot
94
+ end # module Plexus
@@ -0,0 +1,38 @@
1
+ module Plexus
2
+ # An undirected edge is simply an undirected pair (source, target) used in
3
+ # undirected graphs. Edge[u,v] == Edge[v,u]
4
+ class Edge < Arc
5
+
6
+ # Equality allows for the swapping of source and target
7
+ def eql?(other)
8
+ super or (self.class == other.class and target == other.source and source == other.target)
9
+ end
10
+ alias == eql?
11
+
12
+ # Hash is defined such that source and target can be reversed and the
13
+ # hash value will be the same
14
+ def hash
15
+ source.hash ^ target.hash
16
+ end
17
+
18
+ # Sort support
19
+ def <=>(rhs)
20
+ [[source,target].max, [source,target].min] <=> [[rhs.source,rhs.target].max, [rhs.source,rhs.target].min]
21
+ end
22
+
23
+ # Edge[1,2].to_s => "(1=2)"
24
+ # Edge[2,1].to_s => "(1=2)"
25
+ # Edge[2,1,'test'].to_s => "(1=2 test)"
26
+ def to_s
27
+ l = label ? " '#{label.to_s}'" : ''
28
+ s = source.to_s
29
+ t = target.to_s
30
+ "(#{[s,t].min}=#{[s,t].max}#{l})"
31
+ end
32
+
33
+ end
34
+
35
+ class MultiEdge < Edge
36
+ include ArcNumber
37
+ end
38
+ end
@@ -0,0 +1,79 @@
1
+ class Object
2
+ # Get the singleton class of the object.
3
+ # Depending on the object which requested its singleton class,
4
+ # a `module_eval` or a `class_eval` will be performed.
5
+ # Object of special constant type (`Fixnum`, `NilClass`, `TrueClass`,
6
+ # `FalseClass` and `Symbol`) return `nil` as they do not have a
7
+ # singleton class.
8
+ #
9
+ # @return the singleton class
10
+ def singleton_class
11
+ if self.respond_to? :module_eval
12
+ self.module_eval("class << self; self; end")
13
+ elsif self.respond_to? :instance_eval
14
+ begin
15
+ self.instance_eval("class << self; self; end")
16
+ rescue TypeError
17
+ nil
18
+ end
19
+ end
20
+ end
21
+
22
+ # Check wether the object is of the specified kind.
23
+ # If the receiver has a singleton class, will also perform
24
+ # the check on its singleton class' ancestors, so as to catch
25
+ # any included modules for object instances.
26
+ #
27
+ # Example:
28
+ #
29
+ # class A; include Digraph; end
30
+ # a.singleton_class.ancestors
31
+ # # => [Plexus::GraphAPI, Plexus::DirectedGraph::Algorithms, ...
32
+ # Plexus::Labels, Enumerable, Object, Plexus, Kernel, BasicObject]
33
+ # a.is_a? Plexus::Graph
34
+ # # => true
35
+ #
36
+ # @param [Class] klass
37
+ # @return [Boolean]
38
+ def is_a? klass
39
+ sc = self.singleton_class
40
+ if not sc.nil?
41
+ self.singleton_class.ancestors.include?(klass) || super
42
+ else
43
+ super
44
+ end
45
+ end
46
+ end
47
+
48
+ class Module
49
+ # Helper which purpose is, given a class including a module,
50
+ # to make each methods defined within a module's submodule `ClassMethods`
51
+ # available as class methods to the receiving class.
52
+ #
53
+ # Example:
54
+ #
55
+ # module A
56
+ # extends_host
57
+ # module ClassMethods
58
+ # def selfy; puts "class method for #{self}"; end
59
+ # end
60
+ # end
61
+ #
62
+ # class B; include A; end
63
+ #
64
+ # B.selfy
65
+ # # => class method for B
66
+ #
67
+ # @option *params [Symbol] :with (:ClassMethods) the name of the
68
+ # module to extend the receiver with
69
+ def extends_host(*params)
70
+ args = (params.pop if params.last.is_a? Hash) || {}
71
+ @_extension_module = args[:with] || :ClassMethods
72
+
73
+ def included(base)
74
+ unless @_extension_module.nil?
75
+ base.extend(self.const_get(@_extension_module))
76
+ end
77
+ end
78
+ end
79
+ end