diff --git a/.document b/.document index ecf3673..e57b319 100644 --- a/.document +++ b/.document @@ -1,5 +1,3 @@ -README.rdoc lib/**/*.rb -bin/* +spec/**/*.rb features/**/*.feature -LICENSE diff --git a/.gitignore b/.gitignore index c1e0daf..cd5d618 100644 --- a/.gitignore +++ b/.gitignore @@ -1,21 +1,11 @@ -## MAC OS -.DS_Store - -## TEXTMATE -*.tmproj -tmtags - -## EMACS -*~ -\#* -.\#* - -## VIM -*.swp - -## PROJECT::GENERAL -coverage -rdoc -pkg - -## PROJECT::SPECIFIC +.bundle/ +log/*.log +pkg/ +spec/dummy/db/*.sqlite3 +spec/dummy/log/*.log +spec/dummy/tmp/ +tmp* +*.gem +.gitignore +TODO.md +Gemfile.lock diff --git a/.rvmrc b/.rvmrc new file mode 100644 index 0000000..b0c5757 --- /dev/null +++ b/.rvmrc @@ -0,0 +1 @@ +rvm 1.9.2@plexus --create diff --git a/CREDITS.markdown b/CREDITS.md similarity index 100% rename from CREDITS.markdown rename to CREDITS.md diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..c80ee36 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source "http://rubygems.org" + +gemspec diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..c1b0e6f --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,31 @@ +PATH + remote: . + specs: + plexus (0.5.8) + activesupport + facets + plexus + +GEM + remote: http://rubygems.org/ + specs: + activesupport (3.0.9) + diff-lcs (1.1.2) + facets (2.9.1) + rspec (2.6.0) + rspec-core (~> 2.6.0) + rspec-expectations (~> 2.6.0) + rspec-mocks (~> 2.6.0) + rspec-core (2.6.4) + rspec-expectations (2.6.0) + diff-lcs (~> 1.1.2) + rspec-mocks (2.6.0) + yard (0.7.2) + +PLATFORMS + ruby + +DEPENDENCIES + plexus! + rspec + yard diff --git a/LICENSE b/LICENSE index 09adea7..75eef1f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,10 +1,12 @@ +Copyright (c) 2010 Jean-Denis Vauguet + Copyright (c) 2009 Bruce Williams Copyright (c) 2007,2006 Shawn Patrick Garbett Copyright (c) 2002,2004,2005 by Horst Duchene -Copyright (c) 2000,2001 Jeremy Siek, Indiana University (jsiek@osl.iu.edu) +Copyright (c) 2000,2001 Jeremy Siek, Indiana University (jsiek@osl.iu.edu) All rights reserved. diff --git a/README.markdown b/README.markdown deleted file mode 100644 index 56ecba9..0000000 --- a/README.markdown +++ /dev/null @@ -1,183 +0,0 @@ -Graphy: A Graph Theory Library for Ruby -======================================= - -A framework for graph data structures and algorithms. - -This library is based on [GRATR][1] (itself a fork of [RGL][2]). - -Graph algorithms currently provided are: - -* Topological Sort -* Strongly Connected Components -* Transitive Closure -* Rural Chinese Postman -* Biconnected - -These are based on more general algorithm patterns: - -* Breadth First Search -* Depth First Search -* A* Search -* Floyd-Warshall -* Best First Search -* Djikstra's Algorithm -* Lexicographic Search - -The Tour --------- - -### Arcs - -There are two Arc classes, `Graphy::Arc` and `Graphy::Edge`. - -### Graph Types - -There are a number of different graph types, each of which provide -different features and constraints: - -`Graphy::Digraph` and it's pseudonym `Graphy::DirectedGraph`: - -* Single directed edges between vertices -* Loops are forbidden - -`Graphy::DirectedPseudoGraph`: - -* Multiple directed edges between vertices -* Loops are forbidden - -`Graphy::DirectedMultiGraph`: - -* Multiple directed edges between vertices -* Loops on vertices - -`Graphy::UndirectedGraph`, `Graphy::UndirectedPseudoGraph`, and -`Graph::UndirectedMultiGraph` are similar but all edges are undirected. - -### Data Structures - -Use the `Graphy::AdjacencyGraph` module provides a generalized adjacency -list and an edge list adaptor. - -The `Graphy::Digraph` class is the general purpose "swiss army knife" of graph -classes, most of the other classes are just modifications to this class. -It is optimized for efficient access to just the out-edges, fast vertex -insertion and removal at the cost of extra space overhead, etc. - -Example Usage -------------- - -Require the library: - - require 'graphy' - -If you'd like to include all the classes in the current scope (so you -don't have to prefix with `GraphTeory::`), just: - - include Graphy - -Let's play with the library a bit in IRB: - - >> dg = Digraph[1,2, 2,3, 2,4, 4,5, 6,4, 1,6] - => Graphy::Digraph[[2, 3], [1, 6], [2, 4], [4, 5], [1, 2], [6, 4]] - -A few properties of the graph - - >> dg.directed? - => true - >> dg.vertex?(4) - => true - >> dg.edge?(2,4) - => true - >> dg.edge?(4,2) - => false - >> dg.vertices - => [5, 6, 1, 2, 3, 4] - -Every object could be a vertex, even the class object `Object`: - - >> dg.vertex?(Object) - => false - >> UndirectedGraph.new(dg).edges.sort.to_s - => "(1=2)(2=3)(2=4)(4=5)(1=6)(4=6)" - -Add inverse edge `(4-2)` to directed graph: - - >> dg.add_edge!(4,2) - => GRATR::Digraph[[2, 3], [1, 6], [4, 2], [2, 4], [4, 5], [1, 2], [6, 4]] - -`(4-2) == (2-4)` in the undirected graph: - - >> UndirectedGraph.new(dg).edges.sort.to_s - => "(1=2)(2=3)(2=4)(4=5)(1=6)(4=6)" - -`(4-2) != (2-4)` in directed graphs: - - >> dg.edges.sort.to_s - => "(1-2)(1-6)(2-3)(2-4)(4-2)(4-5)(6-4)" - >> dg.remove_edge! 4,2 - => GRATR::Digraph[[2, 3], [1, 6], [2, 4], [4, 5], [1, 2], [6, 4]] - -Topological sorting is realized with an iterator: - - >> dg.topsort - => [1, 2, 3, 6, 4, 5] - >> y = 0; dg.topsort { |v| y += v }; y - => 21 - -You can use DOT to visualize the graph: - - >> require 'graph/dot' - >> dg.write_to_graphic_file('jpg','visualize') - -Here's an example showing the module inheritance hierarchy: - - >> module_graph = Digraph.new - >> ObjectSpace.each_object(Module) do |m| - >> m.ancestors.each {|a| module_graph.add_edge!(m,a) if m != a} - >> end - >> gv = module_graph.vertices.select {|v| v.to_s.match(/Graphy/) } - >> module_graph.induced_subgraph(gv).write_to_graphic_file('jpg','module_graph') - -Look for more in the examples directory. - -History -------- - -This library is based on [GRATR][1] by Shawn Garbett (itself a fork of -Horst Duchene's RGL library) which is heavily influenced by the Boost -Graph Library (BGL). - -This fork attempts to modernize and extend the API and tests. - -References ----------- - -For more information on Graph Theory, you may want to read: - -* The [documentation][3] for the Boost Graph Library -* [The Dictionary of Algorithms and Data Structures][4] - -Credits -------- - -See CREDITS.markdown - -TODO ----- - -See TODO.markdown - -CHANGELOG ---------- - -See CHANGELOG.markdown - -License -------- - -See LICENSE - -[1]: http://gratr.rubyforge.org -[2]: http://rgl.rubyforge.org -[3]: http://www.boost.org/libs/graph/doc -[4]: http://www.nist.gov/dads/HTML/graph.html diff --git a/README.md b/README.md new file mode 100644 index 0000000..34853ec --- /dev/null +++ b/README.md @@ -0,0 +1,208 @@ +# Plexus (was Graphy). A framework for graph theory, graph data structures and associated algorithms. + +Graph algorithms currently provided are: + +* Topological Sort +* Strongly Connected Components +* Transitive Closure +* Rural Chinese Postman +* Biconnected + +These are based on more general algorithm patterns: + +* Breadth First Search +* Depth First Search +* A* Search +* Floyd-Warshall +* Best First Search +* Djikstra's Algorithm +* Lexicographic Search + +## A quick Tour + +### Arcs + +There are two vertices bound classes, `Plexus::Arc` and `Plexus::Edge`. The +former defines directional edges, the latter undirected edges. + +### Vertices + +Vertices can be any `Object`. + +### Graph Types + +There are a number of different graph types, each of which provide +different features and constraints: + +`Plexus::Digraph` and its alias `Plexus::DirectedGraph`: + +* Single directed edges (arcs) between vertices +* Loops are forbidden + +`Plexus::DirectedPseudoGraph`: + +* Multiple directed edges (arcs) between vertices +* Loops are forbidden + +`Plexus::DirectedMultiGraph`: + +* Multiple directed edges (arcs) between vertices +* Loops on vertices + +`Plexus::UndirectedGraph`, `Plexus::UndirectedPseudoGraph`, and +`Graph::UndirectedMultiGraph` are similar but all edges are undirected. + +### Data Structures + +In order to modelize data structures, make use of the `Plexus::AdjacencyGraph` +module which provides a generalized adjacency list and an edge list adaptor. + +The `Plexus::Digraph` class is the general purpose "swiss army knife" of graph +classes, most of the other classes are just modifications to this class. +It is optimized for efficient access to just the out-edges, fast vertex +insertion and removal at the cost of extra space overhead, etc. + +## Example Usage + +Using IRB, first require the library: + +``` bash +require 'rubygems' # only if you are using ruby 1.8.x +require 'plexus' +``` + +If you'd like to include all the classes in the current scope (so you +don't have to prefix with `Plexus::`), just: + +``` bash +include Plexus +``` + +Let's play with the library a bit in IRB: + +``` bash +>> dg = Digraph[1,2, 2,3, 2,4, 4,5, 6,4, 1,6] +=> Plexus::Digraph[[2, 3], [1, 6], [2, 4], [4, 5], [1, 2], [6, 4]] +``` + +A few properties of the graph we just created: + +``` bash +>> dg.directed? +=> true +>> dg.vertex?(4) +=> true +>> dg.edge?(2,4) +=> true +>> dg.edge?(4,2) +=> false +>> dg.vertices +=> [1, 2, 3, 4, 5, 6] +``` + +Every object could be a vertex, even the class object `Object`: + +``` bash +>> dg.vertex?(Object) +=> false + +>> UndirectedGraph.new(dg).edges.sort.to_s +=> "[Plexus::Edge[1,2,nil], Plexus::Edge[2,3,nil], Plexus::Edge[2,4,nil], + Plexus::Edge[4,5,nil], Plexus::Edge[1,6,nil], Plexus::Edge[6,4,nil]]" +``` + +Add inverse edge `(4-2)` to directed graph: + +``` bash +>> dg.add_edge!(4,2) +=> Plexus::DirectedGraph[Plexus::Arc[1,2,nil], Plexus::Arc[1,6,nil], Plexus::Arc[2,3,nil], + Plexus::Arc[2,4,nil], Plexus::Arc[4,5,nil], Plexus::Arc[4,2,nil], + Plexus::Arc[6,4,nil]] +``` + +`(4-2) == (2-4)` in the undirected graph (4-2 doesn't show up): + +``` bash + >> UndirectedGraph.new(dg).edges.sort.to_s + => "[Plexus::Edge[1,2,nil], Plexus::Edge[2,3,nil], Plexus::Edge[2,4,nil], + Plexus::Edge[4,5,nil], Plexus::Edge[1,6,nil], Plexus::Edge[6,4,nil]]" +``` + +`(4-2) != (2-4)` in directed graphs (both show up): + +``` bash +>> dg.edges.sort.to_s +=> "[Plexus::Arc[1,2,nil], Plexus::Arc[1,6,nil], Plexus::Arc[2,3,nil], + Plexus::Arc[2,4,nil], Plexus::Arc[4,2,nil], Plexus::Arc[4,5,nil], + Plexus::Arc[6,4,nil]]" + +>> dg.remove_edge! 4,2 +=> Plexus::DirectedGraph[Plexus::Arc[1,2,nil], Plexus::Arc[1,6,nil], Plexus::Arc[2,3,nil], + Plexus::Arc[2,4,nil], Plexus::Arc[4,5,nil], Plexus::Arc[6,4,nil]] +``` + +Topological sorting is realized with an iterator: + +``` bash +>> dg.topsort +=> [1, 6, 2, 4, 5, 3] +>> y = 0; dg.topsort { |v| y += v }; y +=> 21 +``` + +You can use DOT to visualize the graph: + +``` bash +>> require 'plexus/dot' +>> dg.write_to_graphic_file('jpg','visualize') +``` + +Here's an example showing the module inheritance hierarchy: + +``` bash +>> module_graph = Digraph.new +>> ObjectSpace.each_object(Module) do |m| +>> m.ancestors.each {|a| module_graph.add_edge!(m,a) if m != a} +>> end +>> gv = module_graph.vertices.select {|v| v.to_s.match(/Plexus/) } +>> module_graph.induced_subgraph(gv).write_to_graphic_file('jpg','module_graph') +``` + +Look for more in the examples directory. + +## History + +This library is based on [GRATR][1] by Shawn Garbett (itself a fork of +Horst Duchene's [RGL][2] library) which is heavily influenced by the [Boost][3] +Graph Library (BGL). + +This fork attempts to modernize and extend the API and tests. + +## References + +For more information on Graph Theory, you may want to read: + +* the [documentation][3] for the Boost Graph Library +* [the Dictionary of Algorithms and Data Structures][4] + +## Credits + +See CREDITS.markdown + +## TODO + +See TODO.markdown + +## CHANGELOG + +See CHANGELOG.markdown + +## License + +[MIT License](http://en.wikipedia.org/wiki/MIT_License). See the LICENSE file. + +[1]: http://gratr.rubyforge.org +[2]: http://rgl.rubyforge.org +[3]: http://www.boost.org/libs/graph/doc +[4]: http://www.nist.gov/dads/HTML/graph.html + diff --git a/Rakefile b/Rakefile index b7fcf95..6024f67 100644 --- a/Rakefile +++ b/Rakefile @@ -1,55 +1,25 @@ +# encoding: UTF-8 require 'rubygems' -require 'rake' - begin - require 'jeweler' - Jeweler::Tasks.new do |gem| - gem.name = "graphy" - gem.summary = "A Graph Theory Ruby library" - gem.description =<<-EOD -A framework for graph data structures and algorithms. - -This library is based on GRATR and RGL. - -Graph algorithms currently provided are: - -* Topological Sort -* Strongly Connected Components -* Transitive Closure -* Rural Chinese Postman -* Biconnected - EOD - gem.email = "bruce@codefluency.com" - gem.homepage = "http://github.com/bruce/graphy" - gem.authors = ["Bruce Williams"] - end - Jeweler::GemcutterTasks.new + require 'bundler/setup' rescue LoadError - puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler" + puts 'You must `gem install bundler` and `bundle install` to run rake tasks' end +require 'rake' require 'rake/rdoctask' -Rake::RDocTask.new do |rdoc| - version = File.exist?('VERSION') ? File.read('VERSION') : "" - rdoc.rdoc_dir = 'rdoc' - rdoc.title = "graphy #{version}" - rdoc.rdoc_files.include('README*') - rdoc.rdoc_files.include('lib/**/*.rb') -end +require 'rspec/core' +require 'rspec/core/rake_task' -require 'spec/rake/spectask' -Spec::Rake::SpecTask.new(:spec) do |spec| - spec.libs << 'lib' << 'spec' - spec.spec_files = FileList['spec/**/*_spec.rb'] -end - -Spec::Rake::SpecTask.new(:rcov) do |spec| - spec.libs << 'lib' << 'spec' - spec.pattern = 'spec/**/*_spec.rb' - spec.rcov = true -end - -task :spec => :check_dependencies +RSpec::Core::RakeTask.new(:spec) task :default => :spec + +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'Plexus' + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README.rdoc') + rdoc.rdoc_files.include('lib/**/*.rb') +end diff --git a/TODO.markdown b/TODO.markdown deleted file mode 100644 index 415ce47..0000000 --- a/TODO.markdown +++ /dev/null @@ -1,14 +0,0 @@ -TODO -==== - -From GRATR ----------- - -The following list were present in GRATR, and may (or may not) be -added to this library: - -* Primal Dual for combinatorial optimization problems -* Min-Max Flow -* Near optimal traveling salesman problem -* Orientation of undirected graphs -* Undirected graphs from ActiveRecord diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..3b63a3d --- /dev/null +++ b/TODO.md @@ -0,0 +1,20 @@ +# TODO + +## From the current Graphy project + +* code cleanup: syntax, typo +* YARD documentation +* hunt for buggy behaviors +* a solid helper to check graphy object type in replacement of the current `is_a?` hack +* have the `[]` class method propagate through nested module inclusions [see this topic](http://www.ruby-forum.com/topic/68638) +* [Incremental heuristic search algorithms](http://en.wikipedia.org/wiki/Incremental_heuristic_search)? + +## From GRATR + +The following list was present in GRATR, and items may (or may not) be added to this library: + +* Primal Dual for combinatorial optimization problems +* Min-Max Flow +* Near optimal traveling salesman problem +* Orientation of undirected graphs +* Undirected graphs from ActiveRecord diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..cb0c939 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.5.2 diff --git a/lib/graphy.rb b/lib/graphy.rb deleted file mode 100644 index 0bf4b79..0000000 --- a/lib/graphy.rb +++ /dev/null @@ -1,67 +0,0 @@ -#-- -# Copyright (c) 2006 Shawn Patrick Garbett -# Copyright (c) 2002,2004,2005 by Horst Duchene -# -# 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 Graphy - autoload :AdjacencyGraph, 'graphy/adjacency_graph' - autoload :Arc, 'graphy/arc' - autoload :ArcNumber, 'graphy/arc_number' - autoload :Biconnected, 'graphy/biconnected' - autoload :ChinesePostman, 'graphy/chinese_postman' - autoload :Common, 'graphy/common' - autoload :Comparability, 'graphy/comparability' - - autoload :DirectedGraph, 'graphy/directed_graph' - autoload :Digraph, 'graphy/directed_graph' - autoload :DirectedPseudoGraph, 'graphy/directed_graph' - autoload :DirectedMultiGraph, 'graphy/directed_graph' - - autoload :Dot, 'graphy/dot' - autoload :Edge, 'graphy/edge' - autoload :Graph, 'graphy/graph' - autoload :GraphAPI, 'graphy/graph_api' - autoload :Labels, 'graphy/labels' - autoload :MaximumFlow, 'graphy/maximum_flow' - autoload :Rdot, 'graphy/rdot' - autoload :Search, 'graphy/search' - autoload :StrongComponents, 'graphy/strong_components' - autoload :UndirectedGraph, 'graphy/undirected_graph' -end - -# Vendored libraries - -require 'pathname' - -path = Pathname.new(__FILE__) -$LOAD_PATH.unshift(path + '../../vendor') -$LOAD_PATH.unshift(path + '../../vendor/priority-queue/lib') - -require 'rdot' -require 'priority_queue/ruby_priority_queue' -PriorityQueue = RubyPriorityQueue diff --git a/lib/graphy/adjacency_graph.rb b/lib/graphy/adjacency_graph.rb deleted file mode 100644 index db400ff..0000000 --- a/lib/graphy/adjacency_graph.rb +++ /dev/null @@ -1,159 +0,0 @@ -module Graphy - - # This provides the basic routines needed to implement the Digraph, - # UndirectedGraph, PseudoGraph, DirectedPseudoGraph, MultiGraph and - # DirectedPseudoGraph class. - module AdjacencyGraph - - class ArrayWithAdd < Array # :nodoc: - alias add push - end - - # Initialization parameters can include an Array of edges to add, Graphs to - # copy (will merge if multiple) - # :parallel_edges denotes that duplicate edges are allowed - # :loops denotes that loops are allowed - def implementation_initialize(*params) - @vertex_dict = Hash.new - clear_all_labels - - args = (params.pop if params.last.kind_of? Hash) || {} - - # Basic configuration of adjacency - @allow_loops = args[:loops] || false - @parallel_edges = args[:parallel_edges] || false - @edgelist_class = @parallel_edges ? ArrayWithAdd : Set - if @parallel_edges - @edge_number = Hash.new - @next_edge_number = 0 - end - - # Copy any given graph into this graph - params.select {|p| p.kind_of? Graphy::Graph}.each do |g| - g.edges.each do |e| - add_edge!(e) - edge_label_set(e, edge_label(e)) if edge_label(e) - end - g.vertices.each do |v| - vertex_label_set(v, vertex_label(v)) if vertex_label(v) - end - end - - # Add all array edges specified - params.select {|p| p.kind_of? Array}.each do |a| - 0.step(a.size-1, 2) {|i| add_edge!(a[i], a[i+1])} - end - - end - - # Returns true if v is a vertex of this Graph - # An O(1) implementation of vertex? - def vertex?(v) @vertex_dict.has_key?(v); end - - # Returns true if [u,v] or u is an Arc - # An O(1) implementation - def edge?(u, v=nil) - u, v = u.source, u.target if u.kind_of? Graphy::Arc - vertex?(u) and @vertex_dict[u].include?(v) - end - - # Adds a vertex to the graph with an optional label - def add_vertex!(vertex, label=nil) - @vertex_dict[vertex] ||= @edgelist_class.new - self[vertex] = label if label - self - end - - # Adds an edge to the graph - # Can be called in two basic ways, label is optional - # * add_edge!(Arc[source,target], "Label") - # * add_edge!(source,target, "Label") - def add_edge!(u, v=nil, l=nil, n=nil) - n = u.number if u.class.include? ArcNumber and n.nil? - u, v, l = u.source, u.target, u.label if u.kind_of? Graphy::Arc - return self if not @allow_loops and u == v - n = (@next_edge_number+=1) unless n if @parallel_edges - add_vertex!(u); add_vertex!(v) - @vertex_dict[u].add(v) - (@edge_number[u] ||= @edgelist_class.new).add(n) if @parallel_edges - unless directed? - @vertex_dict[v].add(u) - (@edge_number[v] ||= @edgelist_class.new).add(n) if @parallel_edges - end - self[n ? edge_class[u,v,n] : edge_class[u,v]] = l if l - self - end - - # Removes a given vertex from the graph - def remove_vertex!(v) -# FIXME This is broken for multi graphs - @vertex_dict.delete(v) - @vertex_dict.each_value { |adjList| adjList.delete(v) } - @vertex_dict.keys.each do |u| - delete_label(edge_class[u,v]) - delete_label(edge_class[v,u]) - end - delete_label(v) - self - end - - # Removes an edge from the graph, can be called with source and target or with - # and object of Graphy::Arc derivation - def remove_edge!(u, v=nil) - unless u.kind_of? Graphy::Arc - raise ArgumentError if @parallel_edges - u = edge_class[u,v] - end - raise ArgumentError if @parallel_edges and (u.number || 0) == 0 - return self unless @vertex_dict[u.source] # It doesn't exist - delete_label(u) # Get rid of label - if @parallel_edges - index = @edge_number[u.source].index(u.number) - raise NoArcError unless index - @vertex_dict[u.source].delete_at(index) - @edge_number[u.source].delete_at(index) - else - @vertex_dict[u.source].delete(u.target) - end - self - end - - # Returns an array of vertices that the graph has - def vertices() @vertex_dict.keys; end - - # Returns an array of edges, most likely of class Arc or Edge depending - # upon the type of graph - def edges - @vertex_dict.keys.inject(Set.new) do |a,v| - if @parallel_edges and @edge_number[v] - @vertex_dict[v].zip(@edge_number[v]).each do |w| - s,t,n = v,w[0],w[1] - a.add( edge_class[ s,t,n, edge_label(s,t,n) ] ) - end - else - @vertex_dict[v].each do |w| - a.add(edge_class[v,w,edge_label(v,w)]) - end - end; a - end.to_a - end - -# FIXME, EFFED UP - def adjacent(x, options={}) - options[:direction] ||= :out - if !x.kind_of?(Graphy::Arc) and (options[:direction] == :out || !directed?) - if options[:type] == :edges - i = -1 - @parallel_edges ? - @vertex_dict[x].map {|v| e=edge_class[x,v,@edge_number[x][i+=1]]; e.label = self[e]; e} : - @vertex_dict[x].map {|v| e=edge_class[x,v]; e.label = self[e]; e} - else - @vertex_dict[x].to_a - end - else - graph_adjacent(x,options) - end - end - - end # Adjacency Graph -end # Graphy diff --git a/lib/graphy/arc.rb b/lib/graphy/arc.rb deleted file mode 100644 index d866fb4..0000000 --- a/lib/graphy/arc.rb +++ /dev/null @@ -1,51 +0,0 @@ -module Graphy - - # Arc includes classes for representing egdes of directed and - # undirected graphs. There is no need for a Vertex class, because any ruby - # object can be a vertex of a graph. - # - # Arc's base is a Struct with a :source, a :target and a :label - Struct.new("ArcBase",:source, :target, :label) - - class Arc < Struct::ArcBase - - def initialize(p_source,p_target,p_label=nil) - super(p_source, p_target, p_label) - end - - # Ignore labels for equality - def eql?(other) self.class == other.class and target==other.target and source==other.source; end - - # Alias for eql? - alias == eql? - - # Returns (v,u) if self == (u,v). - def reverse() self.class.new(target, source, label); end - - # Sort support - def <=>(rhs) [source,target] <=> [rhs.source,rhs.target]; end - - # Arc.new[1,2].to_s => "(1-2 'label')" - def to_s - l = label ? " '#{label.to_s}'" : '' - "(#{source}-#{target}#{l})" - end - - # Hash is defined in such a way that label is not - # part of the hash value - def hash() source.hash ^ (target.hash+1); end - - # Shortcut constructor. Instead of Arc.new(1,2) one can use Arc[1,2] - def self.[](p_source, p_target, p_label=nil) - new(p_source, p_target, p_label) - end - - def inspect() "#{self.class.to_s}[#{source.inspect},#{target.inspect},#{label.inspect}]"; end - - end - - class MultiArc < Arc - include ArcNumber - end - -end diff --git a/lib/graphy/arc_number.rb b/lib/graphy/arc_number.rb deleted file mode 100644 index 287d167..0000000 --- a/lib/graphy/arc_number.rb +++ /dev/null @@ -1,33 +0,0 @@ -module Graphy - - # This module provides for internal numbering of edges for differentiating between mutliple edges - module ArcNumber - - attr_accessor :number # Used to differentiate between mutli-edges - - def initialize(p_source,p_target,p_number,p_label=nil) - self.number = p_number - super(p_source, p_target, p_label) - end - - # Returns (v,u) if self == (u,v). - def reverse() self.class.new(target, source, number, label); end - - # Allow for hashing of self loops - def hash() super ^ number.hash; end - def to_s() super + "[#{number}]"; end - def <=>(rhs) (result = super(rhs)) == 0 ? number <=> rhs.number : result; end - def inspect() "#{self.class.to_s}[#{source.inspect},#{target.inspect},#{number.inspect},#{label.inspect}]"; end - def eql?(rhs) super(rhs) and (rhs.number.nil? or number.nil? or number == rhs.number); end - def ==(rhs) eql?(rhs); end - - # Shortcut constructor. Instead of Arc.new(1,2) one can use Arc[1,2] - def self.included(cl) - - def cl.[](p_source, p_target, p_number=nil, p_label=nil) - new(p_source, p_target, p_number, p_label) - end - end - - end # ArcNumber -end # Graphy diff --git a/lib/graphy/common.rb b/lib/graphy/common.rb deleted file mode 100644 index bc35ac5..0000000 --- a/lib/graphy/common.rb +++ /dev/null @@ -1,42 +0,0 @@ -module Graphy - # This class defines a cycle graph of size n - # This is easily done by using the base Graph - # class and implemeting the minimum methods needed to - # make it work. This is a good example to look - # at for making one's own graph classes - class Cycle - - def initialize(n) @size = n; end - def directed?() false; end - def vertices() (1..@size).to_a; end - def vertex?(v) v > 0 and v <= @size; end - def edge?(u,v=nil) - u, v = [u.source, v.target] if u.kind_of? Graphy::Arc - vertex?(u) && vertex?(v) && ((v-u == 1) or (u==@size && v=1)) - end - def edges() Array.new(@size) {|i| Graphy::Edge[i+1, (i+1)==@size ? 1 : i+2]}; end - end - - # This class defines a complete graph of size n - # This is easily done by using the base Graph - # class and implemeting the minimum methods needed to - # make it work. This is a good example to look - # at for making one's own graph classes - class Complete < Cycle - def initialize(n) @size = n; @edges = nil; end - def edges - return @edges if @edges # Cache edges - @edges = [] - @size.times do |u| - @size.times {|v| @edges << Graphy::Edge[u+1, v+1]} - end; @edges - end - def edge?(u,v=nil) - u, v = [u.source, v.target] if u.kind_of? Graphy::Arc - vertex?(u) && vertex?(v) - end - end # Complete - - - -end # Graphy diff --git a/lib/graphy/directed_graph.rb b/lib/graphy/directed_graph.rb deleted file mode 100644 index 44a6662..0000000 --- a/lib/graphy/directed_graph.rb +++ /dev/null @@ -1,37 +0,0 @@ -module Graphy - - class DirectedGraph < Graph - - autoload :Algorithms, "graphy/directed_graph/algorithms" - autoload :Distance, "graphy/directed_graph/distance" - - def initialize(*params) - args = (params.pop if params.last.kind_of? Hash) || {} - args[:algorithmic_category] = DirectedGraph::Algorithms - super *(params << args) - end - end - - # DirectedGraph is just an alias for Digraph should one desire - Digraph = DirectedGraph - - # This is a Digraph that allows for parallel edges, but does not - # allow loops - class DirectedPseudoGraph < DirectedGraph - def initialize(*params) - args = (params.pop if params.last.kind_of? Hash) || {} - args[:parallel_edges] = true - super *(params << args) - end - end - - # This is a Digraph that allows for parallel edges and loops - class DirectedMultiGraph < DirectedPseudoGraph - def initialize(*params) - args = (params.pop if params.last.kind_of? Hash) || {} - args[:loops] = true - super *(params << args) - end - end - -end diff --git a/lib/graphy/directed_graph/algorithms.rb b/lib/graphy/directed_graph/algorithms.rb deleted file mode 100644 index a672f2e..0000000 --- a/lib/graphy/directed_graph/algorithms.rb +++ /dev/null @@ -1,67 +0,0 @@ -module Graphy - # - # Digraph is a directed graph which is a finite set of vertices - # and a finite set of edges connecting vertices. It cannot contain parallel - # edges going from the same source vertex to the same target. It also - # cannot contain loops, i.e. edges that go have the same vertex for source - # and target. - # - # DirectedPseudoGraph is a class that allows for parallel edges, and a - # DirectedMultiGraph is a class that allows for parallel edges and loops - # as well. - class DirectedGraph - - module Algorithms - - include Search - include StrongComponents - include Distance - include ChinesePostman - - # A directed graph is directed by definition - def directed?() true; end - - # A digraph uses the Arc class for edges - def edge_class() @parallel_edges ? Graphy::MultiArc : Graphy::Arc; end - - # Reverse all edges in a graph - def reversal - result = self.class.new - edges.inject(result) {|a,e| a << e.reverse} - vertices.each { |v| result.add_vertex!(v) unless result.vertex?(v) } - result - end - - # Return true if the Graph is oriented. - def oriented? - e = edges - re = e.map {|x| x.reverse} - not e.any? {|x| re.include?(x)} - end - - # Balanced is when the out edge count is equal to the in edge count - def balanced?(v) out_degree(v) == in_degree(v); end - - # Returns out_degree(v) - in_degree(v) - def delta(v) out_degree(v) - in_degree(v); end - - def community(node, direction) - nodes, stack = {}, adjacent(node, :direction => direction) - while n = stack.pop - unless nodes[n.object_id] || node == n - nodes[n.object_id] = n - stack += adjacent(n, :direction => direction) - end - end - nodes.values - end - - def descendants(node) community(node, :out); end - def ancestors(node) community(node, :in ); end - def family(node) community(node, :all); end - - end - - end - -end diff --git a/lib/graphy/directed_graph/distance.rb b/lib/graphy/directed_graph/distance.rb deleted file mode 100644 index a84925d..0000000 --- a/lib/graphy/directed_graph/distance.rb +++ /dev/null @@ -1,162 +0,0 @@ -module Graphy - - class DirectedGraph - - module Distance - - # Shortest path from Jorgen Band-Jensen and Gregory Gutin, - # _DIGRAPHS:_Theory,_Algorithms_and_Applications, pg 53-54 - # - # Requires that the graph be acyclic. If the graph is not - # acyclic, then see dijkstras_algorithm or bellman_ford_moore - # for possible solutions. - # - # start is the starting vertex - # weight can be a Proc, or anything else is accessed using the [] for the - # the label or it defaults to using - # the value stored in the label for the Arc. If it is a Proc it will - # pass the edge to the proc and use the resulting value. - # zero is used for math system with a different definition of zero - # - # Returns a hash with the key being a vertex and the value being the - # distance. A missing vertex from the hash is an infinite distance - # - # Complexity O(n+m) - def shortest_path(start, weight=nil, zero=0) - dist = {start => zero}; path = {} - topsort(start) do |vi| - next if vi == start - dist[vi],path[vi] = adjacent(vi, :direction => :in).map do |vj| - [dist[vj] + cost(vj,vi,weight), vj] - end.min {|a,b| a[0] <=> b[0]} - end; - dist.keys.size == vertices.size ? [dist,path] : nil - end # shortest_path - - # Algorithm from Jorgen Band-Jensen and Gregory Gutin, - # _DIGRAPHS:_Theory,_Algorithms_and_Applications, pg 53-54 - # - # Finds the distances from a given vertex s in a weighted digraph - # to the rest of the vertices, provided all the weights of arcs - # are non-negative. If negative arcs exist in the graph, two - # basic options exist, 1) modify all weights to be positive by - # using an offset, or 2) use the bellman_ford_moore algorithm. - # Also if the graph is acyclic, use the shortest_path algorithm. - # - # weight can be a Proc, or anything else is accessed using the [] for the - # the label or it defaults to using - # the value stored in the label for the Arc. If it is a Proc it will - # pass the edge to the proc and use the resulting value. - # - # zero is used for math system with a different definition of zero - # - # Returns a hash with the key being a vertex and the value being the - # distance. A missing vertex from the hash is an infinite distance - # - # O(n*log(n) + m) complexity - def dijkstras_algorithm(s, weight = nil, zero = 0) - q = vertices; distance = { s => zero }; path = {} - while not q.empty? - v = (q & distance.keys).inject(nil) {|a,k| (!a.nil?) && (distance[a] < distance[k]) ? a : k} - q.delete(v) - (q & adjacent(v)).each do |u| - c = cost(v,u,weight) - if distance[u].nil? or distance[u] > (c+distance[v]) - distance[u] = c + distance[v] - path[u] = v - end - end - end; [distance, path] - end # dijkstras_algorithm - - # Algorithm from Jorgen Band-Jensen and Gregory Gutin, - # _DIGRAPHS:_Theory,_Algorithms_and_Applications, pg 56-58 - # - # Finds the distances from a given vertex s in a weighted digraph - # to the rest of the vertices, provided the graph has no negative cycle. - # If no negative weights exist, then dijkstras_algorithm is more - # efficient in time and space. Also if the graph is acyclic, use the - # shortest_path algorithm. - # - # weight can be a Proc, or anything else is accessed using the [] for the - # the label or it defaults to using - # the value stored in the label for the Arc. If it is a Proc it will - # pass the edge to the proc and use the resulting value. - # - # zero is used for math system with a different definition of zero - # - # Returns a hash with the key being a vertex and the value being the - # distance. A missing vertex from the hash is an infinite distance - # - # O(nm) complexity - def bellman_ford_moore(start, weight = nil, zero = 0) - distance = { start => zero }; path = {} - 2.upto(vertices.size) do - edges.each do |e| - u,v = e[0],e[1] - unless distance[u].nil? - c = cost(u, v, weight)+distance[u] - if distance[v].nil? or c < distance[v] - distance[v] = c - path[v] = u - end - end - end - end; [distance, path] - end # bellman_ford_moore - - # This uses the Floyd-Warshall algorithm to efficiently find - # and record shortest paths at the same time as establishing - # the costs for all vertices in a graph. - # See, S.Skiena, "The Algorithm Design Manual", - # Springer Verlag, 1998 for more details. - # - # Returns a pair of matrices and a hash of delta values. - # The matrices will be indexed by two vertices and are - # implemented as a Hash of Hashes. The first matrix is the cost, the second - # matrix is the shortest path spanning tree. The delta (difference of number - # of in edges and out edges) is indexed by vertex. - # - # weight specifies how an edge weight is determined, if it's a - # Proc the Arc is passed to it, if it's nil it will just use - # the value in the label for the Arc, otherwise the weight is - # determined by applying the [] operator to the value in the - # label for the Arc - # - # zero defines the zero value in the math system used. Defaults - # of course, to 0. This allows for no assumptions to be made - # about the math system and fully functional duck typing. - # - # O(n^3) complexity in time. - def floyd_warshall(weight=nil, zero=0) - c = Hash.new {|h,k| h[k] = Hash.new} - path = Hash.new {|h,k| h[k] = Hash.new} - delta = Hash.new {|h,k| h[k] = 0} - edges.each do |e| - delta[e.source] += 1 - delta[e.target] -= 1 - path[e.source][e.target] = e.target - c[e.source][e.target] = cost(e, weight) - end - vertices.each do |k| - vertices.each do |i| - if c[i][k] - vertices.each do |j| - if c[k][j] && - (c[i][j].nil? or c[i][j] > (c[i][k] + c[k][j])) - path[i][j] = path[i][k] - c[i][j] = c[i][k] + c[k][j] - return nil if i == j and c[i][j] < zero - end - end - end - end - end - [c, path, delta] - end # floyd_warshall - - end # Distance - - end - -end # Graphy diff --git a/lib/graphy/dot.rb b/lib/graphy/dot.rb deleted file mode 100644 index 4ed142c..0000000 --- a/lib/graphy/dot.rb +++ /dev/null @@ -1,56 +0,0 @@ -module Graphy - class Graph - - # Return a DOT::DOTDigraph for directed graphs or a DOT::DOTSubgraph for an - # undirected Graph. _params_ can contain any graph property specified in - # rdot.rb. If an edge or vertex label is a kind of Hash then the keys - # which match +dot+ properties will be used as well. - def to_dot_graph (params = {}) - params['name'] ||= self.class.name.gsub(/:/,'_') - fontsize = params['fontsize'] ? params['fontsize'] : '8' - graph = (directed? ? DOT::DOTDigraph : DOT::DOTSubgraph).new(params) - edge_klass = directed? ? DOT::DOTDirectedArc : DOT::DOTArc - vertices.each do |v| - name = v.to_s - params = {'name' => '"'+name+'"', - 'fontsize' => fontsize, - 'label' => name} - v_label = vertex_label(v) - params.merge!(v_label) if v_label and v_label.kind_of? Hash - graph << DOT::DOTNode.new(params) - end - edges.each do |e| - params = {'from' => '"'+ e.source.to_s + '"', - 'to' => '"'+ e.target.to_s + '"', - 'fontsize' => fontsize } - e_label = edge_label(e) - params.merge!(e_label) if e_label and e_label.kind_of? Hash - graph << edge_klass.new(params) - end - graph - end - - # Output the dot format as a string - def to_dot (params={}) to_dot_graph(params).to_s; end - - # Call +dotty+ for the graph which is written to the file 'graph.dot' - # in the # current directory. - def dotty (params = {}, dotfile = 'graph.dot') - File.open(dotfile, 'w') {|f| f << to_dot(params) } - system('dotty', dotfile) - end - - # Use +dot+ to create a graphical representation of the graph. Returns the - # filename of the graphics file. - def write_to_graphic_file (fmt='png', dotfile='graph') - src = dotfile + '.dot' - dot = dotfile + '.' + fmt - - File.open(src, 'w') {|f| f << self.to_dot << "\n"} - - system( "dot -T#{fmt} #{src} -o #{dot}" ) - dot - end - - end # module Graph -end # module Graphy diff --git a/lib/graphy/edge.rb b/lib/graphy/edge.rb deleted file mode 100644 index 4952b3a..0000000 --- a/lib/graphy/edge.rb +++ /dev/null @@ -1,37 +0,0 @@ -module Graphy - - # An undirected edge is simply an undirected pair (source, target) used in - # undirected graphs. Edge[u,v] == Edge[v,u] - class Edge < Arc - - # Equality allows for the swapping of source and target - def eql?(other) super or (self.class == other.class and target==other.source and source==other.target); end - - # Alias for eql? - alias == eql? - - # Hash is defined such that source and target can be reversed and the - # hash value will be the same - def hash() source.hash ^ target.hash; end - - # Sort support - def <=>(rhs) - [[source,target].max,[source,target].min] <=> - [[rhs.source,rhs.target].max,[rhs.source,rhs.target].min] - end - - # Edge[1,2].to_s == "(1=2 'label)" - def to_s - l = label ? " '#{label.to_s}'" : '' - s = source.to_s - t = target.to_s - "(#{[s,t].min}=#{[s,t].max}#{l})" - end - - end - - class MultiEdge < Edge - include ArcNumber - end - -end diff --git a/lib/graphy/graph.rb b/lib/graphy/graph.rb deleted file mode 100644 index 7c8ba0d..0000000 --- a/lib/graphy/graph.rb +++ /dev/null @@ -1,324 +0,0 @@ -module Graphy - - # Using the functions required by the GraphAPI, it implements all the - # basic functions of a Graph class by using only functions in GraphAPI. - # An actual implementation still needs to be done, as in Digraph or - # UndirectedGraph. - class Graph - include Enumerable - include Labels - - def self.[](*a) self.new.from_array(*a); end - - def initialize(*params) - raise ArgumentError if params.any? do |p| - !(p.kind_of? Graphy::Graph or p.kind_of? Array or p.kind_of? Hash) - end - args = params.last || {} - class << self - self - end.class_eval do - include( args[:implementation] ? args[:implementation] : AdjacencyGraph ) - include( args[:algorithmic_category] ? args[:algorithmic_category] : Digraph ) - include GraphAPI - end - implementation_initialize(*params) - end - - # Shortcut for creating a Graph - # - # Example: Graphy::Graph[1,2, 2,3, 2,4, 4,5].edges.to_a.to_s => - # "(1-2)(2-3)(2-4)(4-5)" - # - # Or as a Hash for specifying lables - # Graphy::Graph[ [:a,:b] => 3, [:b,:c] => 4 ] (Note: Do not use for Multi or Pseudo graphs) - def from_array(*a) - if a.size == 1 and a[0].kind_of? Hash - # Convert to edge class - a[0].each do |k,v| -#FIXME, edge class shouldn't be assume here!!! - if edge_class.include? Graphy::ArcNumber - add_edge!(edge_class[k[0],k[1],nil,v]) - else - add_edge!(edge_class[k[0],k[1],v]) - end - end -#FIXME, edge class shouldn't be assume here!!! - elsif a[0].kind_of? Graphy::Arc - a.each{|e| add_edge!(e); self[e] = e.label} - elsif a.size % 2 == 0 - 0.step(a.size-1, 2) {|i| add_edge!(a[i], a[i+1])} - else - raise ArgumentError - end - self - end - - # Non destructive version of add_vertex!, returns modified copy of Graph - def add_vertex(v, l=nil) x=self.class.new(self); x.add_vertex!(v,l); end - - # Non destructive version add_edge!, returns modified copy of Graph - def add_edge(u, v=nil, l=nil) x=self.class.new(self); x.add_edge!(u,v,l); end - alias add_arc add_edge - - # Non destructive version of remove_vertex!, returns modified copy of Graph - def remove_vertex(v) x=self.class.new(self); x.remove_vertex!(v); end - - # Non destructive version of remove_edge!, returns modified copy of Graph - def remove_edge(u,v=nil) x=self.class.new(self); x.remove_edge!(u,v); end - alias remove_arc remove_edge - - # Return Array of adjacent portions of the Graph - # x can either be a vertex an edge. - # options specifies parameters about the adjacency search - # :type can be either :edges or :vertices (default). - # :direction can be :in, :out(default) or :all. - # - # Note: It is probably more efficently done in the implementation class. - def adjacent(x, options={}) - d = directed? ? (options[:direction] || :out) : :all - - # Discharge the easy ones first - return [x.source] if x.kind_of? Arc and options[:type] == :vertices and d == :in - return [x.target] if x.kind_of? Arc and options[:type] == :vertices and d == :out - return [x.source, x.target] if x.kind_of? Arc and options[:type] != :edges and d == :all - - (options[:type] == :edges ? edges : to_a).select {|u| adjacent?(x,u,d)} - end -#FIXME, THIS IS A HACK AROUND A SERIOUS PROBLEM - alias graph_adjacent adjacent - - - # Add all objects in _a_ to the vertex set. - def add_vertices!(*a) a.each {|v| add_vertex! v}; self; end - - # See add_vertices! - - def add_vertices(*a) x=self.class.new(self); x.add_vertices(*a); self; end - - # Add all edges in the _edges_ Enumerable to the edge set. Elements of the - # Enumerable can be both two-element arrays or instances of DirectedArc or - # UnDirectedArc. - def add_edges!(*args) args.each { |edge| add_edge!(edge) }; self; end - alias add_arcs! add_edges! - - # See add_edge! - def add_edges(*a) x=self.class.new(self); x.add_edges!(*a); self; end - alias add_arcs add_edges - - # Remove all vertices specified by the Enumerable a from the graph by - # calling remove_vertex!. - def remove_vertices!(*a) a.each { |v| remove_vertex! v }; end - - # See remove_vertices! - def remove_vertices(*a) x=self.class.new(self); x.remove_vertices(*a); end - - # Remove all vertices edges by the Enumerable a from the graph by - # calling remove_edge! - def remove_edges!(*a) a.each { |e| remove_edges! e }; end - alias remove_arcs! remove_edges! - - # See remove_edges - def remove_edges(*a) x=self.class.new(self); x.remove_edges(*a); end - alias remove_arcs remove_edges - - # Execute given block for each vertex, provides for methods in Enumerable - def each(&block) vertices.each(&block); end - - # Returns true if _v_ is a vertex of the graph. - # This is a default implementation that is of O(n) average complexity. - # If a subclass uses a hash to store vertices, then this can be - # made into an O(1) average complexity operation. - def vertex?(v) vertices.include?(v); end - - # Returns true if u or (u,v) is an Arc of the graph. - def edge?(*arg) edges.include?(edge_convert(*args)); end - alias arc? edge? - - # Tests two objects to see if they are adjacent. - # direction parameter specifies direction of adjacency, :in, :out, or :all(default) - # All denotes that if there is any adjacency, then it will return true. - # Note that the default is different than adjacent where one is primarily concerned with finding - # all adjacent objects in a graph to a given object. Here the concern is primarily on seeing - # if two objects touch. For vertexes, any edge between the two will usually do, but the direction - # can be specified if need be. - def adjacent?(source, target, direction=:all) - if source.kind_of? Graphy::Arc - raise NoArcError unless edge? source - if target.kind_of? Graphy::Arc - raise NoArcError unless edge? target - (direction != :out and source.source == target.target) or (direction != :in and source.target == target.source) - else - raise NoVertexError unless vertex? target - (direction != :out and source.source == target) or (direction != :in and source.target == target) - end - else - raise NoVertexError unless vertex? source - if target.kind_of? Graphy::Arc - raise NoArcError unless edge? target - (direction != :out and source == target.target) or (direction != :in and source == target.source) - else - raise NoVertexError unless vertex? target - (direction != :out and edge?(target,source)) or (direction != :in and edge?(source,target)) - end - end - end - - # Returns true if the graph has no vertex, i.e. num_vertices == 0. - def empty?() vertices.size.zero?; end - - # Returns true if the given object is a vertex or Arc in the Graph. - # - def include?(x) x.kind_of?(Graphy::Arc) ? edge?(x) : vertex?(x); end - - # Returns the neighboorhood of the given vertex (or Arc) - # This is equivalent to adjacent, but bases type on the type of object. - # direction can be :all, :in, or :out - def neighborhood(x, direction = :all) - adjacent(x, :direction => direction, :type => ((x.kind_of? Graphy::Arc) ? :edges : :vertices )) - end - - # Union of all neighborhoods of vertices (or edges) in the Enumerable x minus the contents of x - # Definition taken from Jorgen Bang-Jensen, Gregory Gutin, _Digraphs: Theory, Algorithms and Applications_, pg 4 - def set_neighborhood(x, direction = :all) - x.inject(Set.new) {|a,v| a.merge(neighborhood(v,direction))}.reject {|v2| x.include?(v2)} - end - - # Union of all set_neighborhoods reachable in p edges - # Definition taken from Jorgen Bang-Jensen, Gregory Gutin, _Digraphs: Theory, Algorithms and Applications_, pg 46 - def closed_pth_neighborhood(w,p,direction=:all) - if p <= 0 - w - elsif p == 1 - (w + set_neighborhood(w,direction)).uniq - else - n = set_neighborhood(w, direction) - (w + n + closed_pth_neighborhood(n,p-1,direction)).uniq - end - end - - # Returns the neighboorhoods reachable in p steps from every vertex (or edge) - # in the Enumerable x - # Definition taken from Jorgen Bang-Jensen, Gregory Gutin, _Digraphs: Theory, Algorithms and Applications_, pg 46 - def open_pth_neighborhood(x, p, direction=:all) - if p <= 0 - x - elsif p == 1 - set_neighborhood(x,direction) - else - set_neighborhood(open_pth_neighborhood(x, p-1, direction),direction) - closed_pth_neighborhood(x,p-1,direction) - end - end - - # Returns the number of out-edges (for directed graphs) or the number of - # incident edges (for undirected graphs) of vertex _v_. - def out_degree(v) adjacent(v, :direction => :out).size; end - - # Returns the number of in-edges (for directed graphs) or the number of - # incident edges (for undirected graphs) of vertex _v_. - def in_degree(v) adjacent(v, :direction => :in ).size; end - - # Returns the sum of the number in and out edges for a vertex - def degree(v) in_degree(v) + out_degree(v); end - - # Minimum in-degree - def min_in_degree() to_a.map {|v| in_degree(v)}.min; end - - # Minimum out-degree - def min_out_degree() to_a.map {|v| out_degree(v)}.min; end - - # Minimum degree of all vertexes - def min_degree() [min_in_degree, min_out_degree].min; end - - # Maximum in-degree - def max_in_degree() vertices.map {|v| in_degree(v)}.max; end - - # Maximum out-degree - def max_out_degree() vertices.map {|v| out_degree(v)}.max; end - - # Minimum degree of all vertexes - def max_degree() [max_in_degree, max_out_degree].max; end - - # Regular - def regular?() min_degree == max_degree; end - - # Returns the number of vertices. - def size() vertices.size; end - - # Synonym for size. - def num_vertices() vertices.size; end - - # Returns the number of edges. - def num_edges() edges.size; end - - # Utility method to show a string representation of the edges of the graph. - def to_s() edges.to_s; end - - # Equality is defined to be same set of edges and directed? - def eql?(g) - return false unless g.kind_of? Graphy::Graph - - (g.directed? == self.directed?) and - (vertices.sort == g.vertices.sort) and - (g.edges.sort == edges.sort) - end - - # Synonym for eql? - def ==(rhs) eql?(rhs); end - - # Merge another graph into this one - def merge(other) - other.vertices.each {|v| add_vertex!(v) } - other.edges.each {|e| add_edge!(e) } - other.edges.each {|e| add_edge!(e.reverse) } if directed? and !other.directed? - self - end - - # A synonym for merge, that doesn't modify the current graph - def +(other) - result = self.class.new(self) - case other - when Graphy::Graph : result.merge(other) - when Graphy::Arc : result.add_edge!(other) - else result.add_vertex!(other) - end - end - - # Remove all vertices in the specified right hand side graph - def -(other) - case other - when Graphy::Graph : induced_subgraph(vertices - other.vertices) - when Graphy::Arc : self.class.new(self).remove_edge!(other) - else self.class.new(self).remove_vertex!(other) - end - end - - # A synonym for add_edge! - def <<(edge) add_edge!(edge); end - - # Return the complement of the current graph - def complement - vertices.inject(self.class.new) do |a,v| - a.add_vertex!(v) - vertices.each {|v2| a.add_edge!(v,v2) unless edge?(v,v2) }; a - end - end - - # Given an array of vertices return the induced subgraph - def induced_subgraph(v) - edges.inject(self.class.new) do |a,e| - ( v.include?(e.source) and v.include?(e.target) ) ? (a << e) : a - end; - end - - def inspect - l = vertices.select {|v| self[v]}.map {|u| "vertex_label_set(#{u.inspect},#{self[u].inspect})"}.join('.') - self.class.to_s + '[' + edges.map {|e| e.inspect}.join(', ') + ']' + (l && l != '' ? '.'+l : '') - end - - private - def edge_convert(*args) args[0].kind_of?(Graphy::Arc) ? args[0] : edge_class[*args]; end - - end # Graph - -end # Graphy diff --git a/lib/graphy/graph_api.rb b/lib/graphy/graph_api.rb deleted file mode 100644 index 2f08acf..0000000 --- a/lib/graphy/graph_api.rb +++ /dev/null @@ -1,30 +0,0 @@ -module Graphy - - # This defines the minimum set of functions required to make a graph class that can - # use the algorithms defined by this library - module GraphAPI - - # Each implementation module must implement the following routines - # * directed?() # Is the graph directed? - # * add_vertex!(v,l=nil) # Add a vertex to the graph and return the graph, l is an optional label - # * add_edge!(u,v=nil,l=nil) # Add an edge to the graph and return the graph. u can be an Arc or Edge or u,v is an edge pair. last parameter is an optional label - # * remove_vertex!(v) # Remove a vertex to the graph and return the graph - # * remove_edge!(u,v=nil) # Remove an edge from the graph and return the graph - # * vertices() # Returns an array of of all vertices - # * edges() # Returns an array of all edges - # * edge_class() # Returns the class used to store edges - def self.included(klass) - [:directed?,:add_vertex!,:add_edge!,:remove_vertex!,:remove_edge!,:vertices,:edges,:edge_class].each do |meth| - raise "Must implement #{meth}" unless klass.instance_methods.include?(meth.to_s) - end - - klass.class_eval do - # Is this right? - alias remove_arc! remove_edge! - alias add_arc! add_edge! - alias arcs edges - alias arc_class edge_class - end - end - end -end diff --git a/lib/graphy/labels.rb b/lib/graphy/labels.rb deleted file mode 100644 index 2fb477d..0000000 --- a/lib/graphy/labels.rb +++ /dev/null @@ -1,76 +0,0 @@ -module Graphy - module Labels - # Return a label for an edge or vertex - def [](u) (u.kind_of? Graphy::Arc) ? edge_label(u) : vertex_label(u); end - - # Set a label for an edge or vertex - def []= (u, value) (u.kind_of? Graphy::Arc) ? edge_label_set(u,value) : vertex_label_set(u, value); end - - # Delete a label entirely - def delete_label(u) (u.kind_of? Graphy::Arc) ? edge_label_delete(u) : vertex_label_delete(u); end - - # Get the label for an edge - def vertex_label(v) vertex_label_dict[v]; end - - # Set the label for an edge - def vertex_label_set(v, l) vertex_label_dict[v] = l; self; end - - # Get the label for an edge - def edge_label(u,v=nil,n=nil) - u = edge_convert(u,v,n) - edge_label_dict[u] - end - - # Set the label for an edge - def edge_label_set(u, v=nil, l=nil, n=nil) - u.kind_of?(Graphy::Arc) ? l = v : u = edge_convert(u,v,n) - edge_label_dict[u] = l; self - end - - # Delete all graph labels - def clear_all_labels() @vertex_labels = {}; @edge_labels = {}; end - - # Delete an edge label - def edge_label_delete(u, v=nil, n=nil) - u = edge_convert(u,v,n) - edge_label_dict.delete(u) - end - - # Delete a vertex label - def vertex_label_delete(v) vertex_label_dict.delete(v); end - - protected - - def vertex_label_dict() @vertex_labels ||= {}; end - def edge_label_dict() @edge_labels ||= {}; end - - # A generic cost function. It either calls the weight function with and edge - # constructed from the two nodes, or calls the [] operator of the label - # when given a value. If no weight value is specified, the label itself is - # treated as the cost value. - # - # Note: This function will not work for Pseudo or Multi graphs at present. - # FIXME: Remove u,v interface to fix Pseudo Multi graph problems. - def cost(u,v=nil,weight=nil) - u.kind_of?(Arc) ? weight = v : u = edge_class[u,v] - case weight - when Proc : weight.call(u) - when nil : self[u] - else self[u][weight] - end - end - - # An alias of cost for property retrieval in general - alias property cost - - # A function to set properties specified by the user. - def property_set(u,name,value) - case name - when Proc : name.call(value) - when nil : self[u] = value - else self[u][name] = value - end - end - - end -end diff --git a/lib/graphy/search.rb b/lib/graphy/search.rb deleted file mode 100644 index 28dc086..0000000 --- a/lib/graphy/search.rb +++ /dev/null @@ -1,401 +0,0 @@ -module Graphy - module Search - - # Options are mostly callbacks passed in as a hash. - # The following are valid, anything else is ignored - # :enter_vertex => Proc Called upon entry of a vertex - # :exit_vertex => Proc Called upon exit of a vertex - # :root_vertex => Proc Called when a vertex the a root of a tree - # :start_vertex => Proc Called for the first vertex of the search - # :examine_edge => Proc Called when an edge is examined - # :tree_edge => Proc Called when the edge is a member of the tree - # :back_edge => Proc Called when the edge is a back edge - # :forward_edge => Proc Called when the edge is a forward edge - # :adjacent => Proc that given a vertex returns adjacent nodes, defaults to adjacent call of graph useful for changing the definition of adjacent in some algorithms - # - # :start => Vertex Specifies the vertex to start search from - # - # If a &block is specified it defaults to :enter_vertex - # - # Returns the list of vertexes as reached by enter_vertex - # This allows for calls like, g.bfs.each {|v| ...} - # - # Can also be called like bfs_examine_edge {|e| ... } or - # dfs_back_edge {|e| ... } for any of the callbacks - # - # A full example usage is as follows: - # - # ev = Proc.new {|x| puts "Enter Vertex #{x}"} - # xv = Proc.new {|x| puts "Exit Vertex #{x}"} - # sv = Proc.new {|x| puts "Start Vertex #{x}"} - # ee = Proc.new {|x| puts "Examine Arc #{x}"} - # te = Proc.new {|x| puts "Tree Arc #{x}"} - # be = Proc.new {|x| puts "Back Arc #{x}"} - # fe = Proc.new {|x| puts "Forward Arc #{x}"} - # Digraph[1,2,2,3,3,4].dfs({ - # :enter_vertex => ev, - # :exit_vertex => xv, - # :start_vertex => sv, - # :examine_edge => ee, - # :tree_edge => te, - # :back_edge => be, - # :forward_edge => fe }) - # - # Which outputs: - # - # Start Vertex 1 - # Enter Vertex 1 - # Examine Arc (1=2) - # Tree Arc (1=2) - # Enter Vertex 2 - # Examine Arc (2=3) - # Tree Arc (2=3) - # Enter Vertex 3 - # Examine Arc (3=4) - # Tree Arc (3=4) - # Enter Vertex 4 - # Examine Arc (1=4) - # Back Arc (1=4) - # Exit Vertex 4 - # Exit Vertex 3 - # Exit Vertex 2 - # Exit Vertex 1 - def bfs(options={}, &block) graphy_search_helper(:shift, options, &block); end - - # See options for bfs method - def dfs(options={}, &block) graphy_search_helper(:pop, options, &block); end - - # Routine to compute a spanning forest for the given search method - # Returns two values, first is a hash of predecessors and second an array of root nodes - def spanning_forest(start, routine) - predecessor = {} - roots = [] - te = Proc.new {|e| predecessor[e.target] = e.source} - rv = Proc.new {|v| roots << v} - send routine, :start => start, :tree_edge => te, :root_vertex => rv - [predecessor, roots] - end - - # Return the dfs spanning forest for the given start node, see spanning_forest - def dfs_spanning_forest(start) spanning_forest(start, :dfs); end - - # Return the bfs spanning forest for the given start node, see spanning_forest - def bfs_spanning_forest(start) spanning_forest(start, :bfs); end - - # Returns a hash of predecessors in a tree rooted at the start node. If this is a connected graph - # then it will be a spanning tree and contain all vertices. An easier way to tell if it's a spanning tree is to - # use a spanning_forest call and check if there is a single root node. - def tree_from_vertex(start, routine) - predecessor={} - correct_tree = false - te = Proc.new {|e| predecessor[e.target] = e.source if correct_tree} - rv = Proc.new {|v| correct_tree = (v == start)} - send routine, :start => start, :tree_edge => te, :root_vertex => rv - predecessor - end - - # Returns a hash of predecessors for the depth first search tree rooted at the given node - def dfs_tree_from_vertex(start) tree_from_vertex(start, :dfs); end - - # Returns a hash of predecessors for the depth first search tree rooted at the given node - def bfs_tree_from_vertex(start) tree_from_vertex(start, :bfs); end - - # An inner class used for greater efficiency in lexicograph_bfs - # - # Original desgn taken from Golumbic's, "Algorithmic Graph Theory and - # Perfect Graphs" pg, 87-89 - class LexicographicQueue - - # Called with the initial values (array) - def initialize(values) - @node = Struct.new(:back, :forward, :data) - @node.class_eval { def hash() @hash; end; @@cnt=0 } - @set = {} - @tail = @node.new(nil, nil, Array.new(values)) - @tail.instance_eval { @hash = (@@cnt+=1) } - values.each {|a| @set[a] = @tail} - end - - # Pop an entry with maximum lexical value from queue - def pop() - return nil unless @tail - value = @tail[:data].pop - @tail = @tail[:forward] while @tail and @tail[:data].size == 0 - @set.delete(value); value - end - - # Increase lexical value of given values (array) - def add_lexeme(values) - fix = {} - values.select {|v| @set[v]}.each do |w| - sw = @set[w] - if fix[sw] - s_prime = sw[:back] - else - s_prime = @node.new(sw[:back], sw, []) - s_prime.instance_eval { @hash = (@@cnt+=1) } - @tail = s_prime if @tail == sw - sw[:back][:forward] = s_prime if sw[:back] - sw[:back] = s_prime - fix[sw] = true - end - s_prime[:data] << w - sw[:data].delete(w) - @set[w] = s_prime - end - fix.keys.select {|n| n[:data].size == 0}.each do |e| - e[:forward][:back] = e[:back] if e[:forward] - e[:back][:forward] = e[:forward] if e[:back] - end - end - - end - - # Lexicographic breadth-first search, the usual queue of vertices - # is replaced by a queue of unordered subsets of the vertices, - # which is sometimes refined but never reordered. - # - # Originally developed by Rose, Tarjan, and Leuker, "Algorithmic - # aspects of vertex elimination on graphs", SIAM J. Comput. 5, 266-283 - # MR53 #12077 - # - # Implementation taken from Golumbic's, "Algorithmic Graph Theory and - # Perfect Graphs" pg, 84-90 - def lexicograph_bfs(&block) - lex_q = Graphy::Graph::Search::LexicographicQueue.new(vertices) - result = [] - num_vertices.times do - v = lex_q.pop - result.unshift(v) - lex_q.add_lexeme(adjacent(v)) - end - result.each {|r| block.call(r)} if block - result - end - - - # A* Heuristic best first search - # - # start is the starting vertex for the search - # - # func is a Proc that when passed a vertex returns the heuristic - # weight of sending the path through that node. It must always - # be equal to or less than the true cost - # - # options are mostly callbacks passed in as a hash, the default block is - # :discover_vertex and weight is assumed to be the label for the Arc. - # The following options are valid, anything else is ignored. - # - # * :weight => can be a Proc, or anything else is accessed using the [] for the - # the label or it defaults to using - # the value stored in the label for the Arc. If it is a Proc it will - # pass the edge to the proc and use the resulting value. - # * :discover_vertex => Proc invoked when a vertex is first discovered - # and is added to the open list. - # * :examine_vertex => Proc invoked when a vertex is popped from the - # queue (i.e., it has the lowest cost on the open list). - # * :examine_edge => Proc invoked on each out-edge of a vertex - # immediately after it is examined. - # * :edge_relaxed => Proc invoked on edge (u,v) if d[u] + w(u,v) < d[v]. - # * :edge_not_relaxed=> Proc invoked if the edge is not relaxed (see above). - # * :black_target => Proc invoked when a vertex that is on the closed - # list is "rediscovered" via a more efficient path, and is re-added - # to the OPEN list. - # * :finish_vertex => Proc invoked on a vertex when it is added to the - # closed list, which happens after all of its out edges have been - # examined. - # - # Returns array of nodes in path, or calls block on all nodes, - # upon failure returns nil - # - # Can also be called like astar_examine_edge {|e| ... } or - # astar_edge_relaxed {|e| ... } for any of the callbacks - # - # The criteria for expanding a vertex on the open list is that it has the - # lowest f(v) = g(v) + h(v) value of all vertices on open. - # - # The time complexity of A* depends on the heuristic. It is exponential - # in the worst case, but is polynomial when the heuristic function h - # meets the following condition: |h(x) - h*(x)| < O(log h*(x)) where h* - # is the optimal heuristic, i.e. the exact cost to get from x to the goal. - # - # Also see: http://en.wikipedia.org/wiki/A-star_search_algorithm - # - def astar(start, goal, func, options, &block) - options.instance_eval "def handle_callback(sym,u) self[sym].call(u) if self[sym]; end" - - # Initialize - d = { start => 0 } - - color = {start => :gray} # Open is :gray, Closed is :black - parent = Hash.new {|k| parent[k] = k} - f = {start => func.call(start)} - queue = PriorityQueue.new.push(start,f[start]) - block.call(start) if block - - # Process queue - until queue.empty? - u,dummy = queue.delete_min - options.handle_callback(:examine_vertex, u) - - # Unravel solution if goal is reached. - if u == goal - solution = [goal] - while u != start - solution << parent[u]; u = parent[u] - end - return solution.reverse - end - - adjacent(u, :type => :edges).each do |e| - v = e.source == u ? e.target : e.source - options.handle_callback(:examine_edge, e) - w = cost(e, options[:weight]) - raise ArgumentError unless w - if d[v].nil? or (w + d[u]) < d[v] - options.handle_callback(:edge_relaxed, e) - d[v] = w + d[u] - f[v] = d[v] + func.call(v) - parent[v] = u - unless color[v] == :gray - options.handle_callback(:black_target, v) if color[v] == :black - color[v] = :gray - options.handle_callback(:discover_vertex, v) - queue.push v, f[v] - block.call(v) if block - end - else - options.handle_callback(:edge_not_relaxed, e) - end - end # adjacent(u) - color[u] = :black - options.handle_callback(:finish_vertex,u) - end # queue.empty? - - nil # failure, on fall through - - end # astar - - # Best first has all the same options as astar with func set to h(v) = 0. - # There is an additional option zero which should be defined to zero - # for the operation '+' on the objects used in the computation of cost. - # The parameter zero defaults to 0. - def best_first(start, goal, options, zero=0, &block) - func = Proc.new {|v| zero} - astar(start, goal, func, options, &block) - end - - alias_method :pre_search_method_missing, :method_missing # :nodoc: - def method_missing(sym,*args, &block) # :nodoc: - m1=/^dfs_(\w+)$/.match(sym.to_s) - dfs((args[0] || {}).merge({m1.captures[0].to_sym => block})) if m1 - m2=/^bfs_(\w+)$/.match(sym.to_s) - bfs((args[0] || {}).merge({m2.captures[0].to_sym => block})) if m2 - pre_search_method_missing(sym, *args, &block) unless m1 or m2 - end - - private - - def graphy_search_helper(op, options={}, &block) # :nodoc: - return nil if size == 0 - result = [] - # Create options hash that handles callbacks - options = {:enter_vertex => block, :start => to_a[0]}.merge(options) - options.instance_eval "def handle_vertex(sym,u) self[sym].call(u) if self[sym]; end" - options.instance_eval "def handle_edge(sym,e) self[sym].call(e) if self[sym]; end" - # Create waiting list that is a queue or stack depending on op specified. - # First entry is the start vertex. - waiting = [options[:start]] - waiting.instance_eval "def next() #{op.to_s}; end" - # Create color map with all set to unvisited except for start vertex - # will be set to waiting - color_map = vertices.inject({}) {|a,v| a[v] = :unvisited; a} - color_map.merge!(waiting[0] => :waiting) - options.handle_vertex(:start_vertex, waiting[0]) - options.handle_vertex(:root_vertex, waiting[0]) - # Perform the actual search until nothing is waiting - until waiting.empty? - # Loop till the search iterator exhausts the waiting list - visited_edges={} # This prevents retraversing edges in undirected graphs - until waiting.empty? - graphy_search_iteration(options, waiting, color_map, visited_edges, result, op == :pop) - end - # Waiting list is exhausted, see if a new root vertex is available - u=color_map.detect {|key,value| value == :unvisited} - waiting.push(u[0]) if u - options.handle_vertex(:root_vertex, u[0]) if u - end; result - end - - def graphy_search_iteration(options, waiting, color_map, visited_edges, result, recursive=false) # :nodoc: - # Get the next waiting vertex in the list - u = waiting.next - options.handle_vertex(:enter_vertex,u) - result << u - # Examine all adjacent outgoing edges, not previously traversed - adj_proc = options[:adjacent] || self.method(:adjacent).to_proc - adj_proc.call(u,:type => :edges, :direction => :out).reject {|w| visited_edges[w]}.each do |e| - e = e.reverse unless directed? or e.source == u # Preserves directionality where required - v = e.target - options.handle_edge(:examine_edge, e) - visited_edges[e]=true - case color_map[v] - # If it's unvisited it goes into the waiting list - when :unvisited - options.handle_edge(:tree_edge, e) - color_map[v] = :waiting - waiting.push(v) - # If it's recursive (i.e. dfs) then call self - graphy_search_iteration(options, waiting, color_map, visited_edges, result, true) if recursive - when :waiting - options.handle_edge(:back_edge, e) - else - options.handle_edge(:forward_edge, e) - end - end - # Finished with this vertex - options.handle_vertex(:exit_vertex, u) - color_map[u] = :visited - end - - public - # Topological Sort Iterator - # - # The topological sort algorithm creates a linear ordering of the vertices - # such that if edge (u,v) appears in the graph, then u comes before v in - # the ordering. The graph must be a directed acyclic graph (DAG). - # - # The iterator can also be applied to undirected graph or to a DG graph - # which contains a cycle. In this case, the Iterator does not reach all - # vertices. The implementation of acyclic? and cyclic? uses this fact. - # - # Can be called with a block as a standard Ruby iterator, or it can - # be used directly as it will return the result as an Array - def topsort(start = nil, &block) - result = [] - go = true - back = Proc.new {|e| go = false } - push = Proc.new {|v| result.unshift(v) if go} - start ||= vertices[0] - dfs({:exit_vertex => push, :back_edge => back, :start => start}) - result.each {|v| block.call(v)} if block; result - end - - # Does a top sort, but trudges forward if a cycle occurs. Use with caution. - def sort(start = nil, &block) - result = [] - push = Proc.new {|v| result.unshift(v)} - start ||= vertices[0] - dfs({:exit_vertex => push, :start => start}) - result.each {|v| block.call(v)} if block; result - end - - # Returns true if a graph contains no cycles, false otherwise - def acyclic?() topsort.size == size; end - - # Returns false if a graph contains no cycles, true otherwise - def cyclic?() not acyclic?; end - - - end # Search -end # Graphy diff --git a/lib/graphy/undirected_graph.rb b/lib/graphy/undirected_graph.rb deleted file mode 100644 index 1119b2f..0000000 --- a/lib/graphy/undirected_graph.rb +++ /dev/null @@ -1,33 +0,0 @@ -module Graphy - - class UndirectedGraph < Graph - - autoload :Algorithms, "graphy/undirected_graph/algorithms" - - def initialize(*params) - args = (params.pop if params.last.kind_of? Hash) || {} - args[:algorithmic_category] = UndirectedGraph::Algorithms - super *(params << args) - end - end - - # This is a Digraph that allows for parallel edges, but does not - # allow loops - class UndirectedPseudoGraph < UndirectedGraph - def initialize(*params) - args = (params.pop if params.last.kind_of? Hash) || {} - args[:parallel_edges] = true - super *(params << args) - end - end - - # This is a Digraph that allows for parallel edges and loops - class UndirectedMultiGraph < UndirectedPseudoGraph - def initialize(*params) - args = (params.pop if params.last.kind_of? Hash) || {} - args[:loops] = true - super *(params << args) - end - end - -end # Graphy diff --git a/lib/plexus.rb b/lib/plexus.rb new file mode 100644 index 0000000..66c1520 --- /dev/null +++ b/lib/plexus.rb @@ -0,0 +1,88 @@ +#-- +# Copyright (c) 2006 Shawn Patrick Garbett +# Copyright (c) 2002,2004,2005 by Horst Duchene +# +# 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 Plexus + # Plexus internals: graph builders and additionnal behaviors + autoload :GraphBuilder, 'plexus/graph' + autoload :AdjacencyGraphBuilder, 'plexus/adjacency_graph' + + autoload :DirectedGraphBuilder, 'plexus/directed_graph' + autoload :DigraphBuilder, 'plexus/directed_graph' + autoload :DirectedPseudoGraphBuilder, 'plexus/directed_graph' + autoload :DirectedMultiGraphBuilder, 'plexus/directed_graph' + + autoload :UndirectedGraphBuilder, 'plexus/undirected_graph' + autoload :UndirectedPseudoGraphBuilder, 'plexus/undirected_graph' + autoload :UndirectedMultiGraphBuilder, 'plexus/undirected_graph' + + autoload :Arc, 'plexus/arc' + autoload :ArcNumber, 'plexus/arc_number' + autoload :Biconnected, 'plexus/biconnected' + autoload :ChinesePostman, 'plexus/chinese_postman' + autoload :Common, 'plexus/common' + autoload :Comparability, 'plexus/comparability' + + autoload :Dot, 'plexus/dot' + autoload :Edge, 'plexus/edge' + autoload :Labels, 'plexus/labels' + autoload :MaximumFlow, 'plexus/maximum_flow' + #autoload :Rdot, 'plexus/dot' + autoload :Search, 'plexus/search' + autoload :StrongComponents, 'plexus/strong_components' + + # Plexus classes + autoload :AdjacencyGraph, 'plexus/classes/graph_classes' + autoload :DirectedGraph, 'plexus/classes/graph_classes' + autoload :Digraph, 'plexus/classes/graph_classes' + autoload :DirectedPseudoGraph, 'plexus/classes/graph_classes' + autoload :DirectedMultiGraph, 'plexus/classes/graph_classes' + autoload :UndirectedGraph, 'plexus/classes/graph_classes' + autoload :UndirectedPseudoGraph, 'plexus/classes/graph_classes' + autoload :UndirectedMultiGraph, 'plexus/classes/graph_classes' + + # ruby stdlib extensions + require 'plexus/ext' + # ruby 1.8.x/1.9.x compatibility + require 'plexus/ruby_compatibility' +end + +# Vendored libraries + +require 'pathname' +path = Pathname.new(__FILE__) +$LOAD_PATH.unshift(path + '../../vendor') # http://ruby.brian-amberg.de/priority-queue/ +$LOAD_PATH.unshift(path + '../../vendor/priority-queue/lib') + +require 'rdot' +require 'facets/hash' + +require 'priority_queue/ruby_priority_queue' +PriorityQueue = RubyPriorityQueue + diff --git a/lib/plexus/adjacency_graph.rb b/lib/plexus/adjacency_graph.rb new file mode 100644 index 0000000..f04bf6b --- /dev/null +++ b/lib/plexus/adjacency_graph.rb @@ -0,0 +1,226 @@ +module Plexus + + # This module provides the basic routines needed to implement the specialized builders: + # {DigraphBuilder}, {UndirectedGraphBuilder}, {DirectedPseudoGraphBuilder}, + # {UndirectedPseudoGraphBuilder}, {DirectedMultiGraphBuilder} and {UndirectedMultiGraphBuilder} + # modules, each of them streamlining {AdjacencyGraphBuilder}'s behavior. Those + # implementations rely on the {GraphBuilder}. + module AdjacencyGraphBuilder + + # Defines a useful `push` -> `add` alias for arrays. + class ArrayWithAdd < Array + alias add push + end + + # This method is called by the specialized implementations + # upon graph creation. + # + # Initialization parameters can include: + # + # * an array of edges to add + # * one or several graphs to copy (will be merged if multiple) + # * `:parallel_edges` denotes that duplicate edges are allowed + # * `:loops denotes` that loops are allowed + # + # @param *params [Hash] the initialization parameters + # + def implementation_initialize(*params) + @vertex_dict = Hash.new + clear_all_labels + + # FIXME: could definitely make use of the activesupport helper + # extract_options! and facets' reverse_merge! technique + # to handle parameters + args = (params.pop if params.last.is_a? Hash) || {} + + # Basic configuration of adjacency. + @allow_loops = args[:loops] || false + @parallel_edges = args[:parallel_edges] || false + @edgelist_class = @parallel_edges ? ArrayWithAdd : Set + if @parallel_edges + @edge_number = Hash.new + @next_edge_number = 0 + end + + # Copy any given graph into this graph. + params.select { |p| p.is_a? Plexus::GraphBuilder }.each do |g| + g.edges.each do |e| + add_edge!(e) + edge_label_set(e, edge_label(e)) if edge_label(e) + end + g.vertices.each do |v| + add_vertex!(v) + vertex_label_set(v, vertex_label(v)) if vertex_label(v) + end + end + + # Add all array edges specified. + params.select { |p| p.is_a? Array }.each do |a| + 0.step(a.size-1, 2) { |i| add_edge!(a[i], a[i+1]) } + end + end + + # Returns true if v is a vertex of this Graph + # (an "O(1)" implementation of `vertex?`). + # + # @param [vertex] v + # @return [Boolean] + def vertex?(v) + @vertex_dict.has_key?(v) + end + + # Returns true if [u,v] or u is an {Arc} + # (an "O(1)" implementation of `edge?`). + # + # @param [vertex] u + # @param [vertex] v (nil) + # @return [Boolean] + def edge?(u, v = nil) + u, v = u.source, u.target if u.is_a? Plexus::Arc + vertex?(u) and @vertex_dict[u].include?(v) + end + + # Adds a vertex to the graph with an optional label. + # + # @param [vertex(Object)] vertex any kind of Object can act as a vertex + # @param [#to_s] label (nil) + def add_vertex!(vertex, label = nil) + @vertex_dict[vertex] ||= @edgelist_class.new + self[vertex] = label if label + self + end + + # Adds an edge to the graph. + # + # Can be called in two basic ways, label is optional: + # @overload add_edge!(arc) + # Using an explicit {Arc} + # @param [Arc] arc an {Arc}[source, target, label = nil] object + # @return [AdjacencyGraph] `self` + # @overload add_edge!(source, target, label = nil) + # Using vertices to define an arc implicitly + # @param [vertex] u + # @param [vertex] v (nil) + # @param [Label] l (nil) + # @param [Integer] n (nil) {Arc arc} number of `(u, v)` (if `nil` and if `u` + # has an {ArcNumber}, then it will be used) + # @return [AdjacencyGraph] `self` + # + def add_edge!(u, v = nil, l = nil, n = nil) + n = u.number if u.class.include? ArcNumber and n.nil? + u, v, l = u.source, u.target, u.label if u.is_a? Plexus::Arc + + return self if !@allow_loops && u == v + + n = (@next_edge_number += 1) unless n if @parallel_edges + add_vertex!(u) + add_vertex!(v) + @vertex_dict[u].add(v) + (@edge_number[u] ||= @edgelist_class.new).add(n) if @parallel_edges + + unless directed? + @vertex_dict[v].add(u) + (@edge_number[v] ||= @edgelist_class.new).add(n) if @parallel_edges + end + + self[n ? edge_class[u,v,n] : edge_class[u,v]] = l if l + self + end + + # Removes a given vertex from the graph. + # + # @param [vertex] v + # @return [AdjacencyGraph] `self` + def remove_vertex!(v) + # FIXME This is broken for multi graphs + @vertex_dict.delete(v) + @vertex_dict.each_value { |adjList| adjList.delete(v) } + @vertex_dict.keys.each do |u| + delete_label(edge_class[u,v]) + delete_label(edge_class[v,u]) + end + delete_label(v) + self + end + + # Removes an edge from the graph. + # + # Can be called with both source and target as vertex, + # or with source and object of {Plexus::Arc} derivation. + # + # @overload remove_edge!(a) + # @param [Plexus::Arc] a + # @return [AdjacencyGraph] `self` + # @raise [ArgumentError] if parallel edges are enabled + # @overload remove_edge!(u, v) + # @param [vertex] u + # @param [vertex] v + # @return [AdjacencyGraph] `self` + # @raise [ArgumentError] if parallel edges are enabled and the {ArcNumber} of `u` is zero + def remove_edge!(u, v = nil) + unless u.is_a? Plexus::Arc + raise ArgumentError if @parallel_edges + u = edge_class[u,v] + end + raise ArgumentError if @parallel_edges and (u.number || 0) == 0 + return self unless @vertex_dict[u.source] # It doesn't exist + delete_label(u) # Get rid of label + if @parallel_edges + index = @edge_number[u.source].index(u.number) + raise NoArcError unless index + @vertex_dict[u.source].delete_at(index) + @edge_number[u.source].delete_at(index) + else + @vertex_dict[u.source].delete(u.target) + end + self + end + + # Returns an array of vertices that the graph has. + # + # @return [Array] graph's vertices + def vertices + @vertex_dict.keys + end + + # Returns an array of edges, most likely of class {Arc} or {Edge} depending + # upon the type of graph. + # + # @return [Array] + def edges + @vertex_dict.keys.inject(Set.new) do |a,v| + if @parallel_edges and @edge_number[v] + @vertex_dict[v].zip(@edge_number[v]).each do |w| + s, t, n = v, w[0], w[1] + a.add(edge_class[s, t, n, edge_label(s, t, n)]) + end + else + @vertex_dict[v].each do |w| + a.add(edge_class[v, w, edge_label(v, w)]) + end + end + a + end.to_a + end + + # FIXME, EFFED UP (but why?) + # + # @fixme + def adjacent(x, options = {}) + options[:direction] ||= :out + + if !x.is_a?(Plexus::Arc) and (options[:direction] == :out || !directed?) + if options[:type] == :edges + i = -1 + @parallel_edges ? + @vertex_dict[x].map { |v| e = edge_class[x, v, @edge_number[x][i+=1]]; e.label = self[e]; e } : + @vertex_dict[x].map { |v| e = edge_class[x, v]; e.label = self[e]; e } + else + @vertex_dict[x].to_a + end + else + graph_adjacent(x,options) + end + end + end +end diff --git a/lib/plexus/arc.rb b/lib/plexus/arc.rb new file mode 100644 index 0000000..54233e3 --- /dev/null +++ b/lib/plexus/arc.rb @@ -0,0 +1,61 @@ +module Plexus + # Arc includes classes for representing egdes of directed and + # undirected graphs. There is no need for a Vertex class, because any ruby + # object can be a vertex of a graph. + # + # Arc's base is a Struct with a :source, a :target and a :label. + Struct.new("ArcBase", :source, :target, :label) + + class Arc < Struct::ArcBase + def initialize(p_source, p_target, p_label = nil) + super(p_source, p_target, p_label) + end + + # Ignore labels for equality. + def eql?(other) + same_class = self.class.ancestors.include?(other.class) || other.class.ancestors.include?(self.class) + same_class && target == other.target && source == other.source + end + alias == eql? + + # Returns (v,u) if self == (u,v). + def reverse() + self.class.new(target, source, label) + end + + # Sort support. + def <=>(rhs) + [source, target] <=> [rhs.source, rhs.target] + end + + # Arc[1,2].to_s => "(1-2)" + # Arc[1,2,'test'].to_s => "(1-2 test)" + def to_s + l = label ? " '#{label.to_s}'" : '' + "(#{source}-#{target}#{l})" + end + + # Hash is defined in such a way that label is not + # part of the hash value + # FIXME: I had to get rid of that in order to make to_dot_graph + # work, but I can't figure it out (doesn't show up in the stack!) + def hash + source.hash ^ (target.hash + 1) + end + + # Shortcut constructor. + # + # Instead of Arc.new(1,2) one can use Arc[1,2]. + def self.[](p_source, p_target, p_label = nil) + new(p_source, p_target, p_label) + end + + def inspect + "#{self.class.to_s}[#{source.inspect},#{target.inspect},#{label.inspect}]" + end + end + + class MultiArc < Arc + include ArcNumber + end +end diff --git a/lib/plexus/arc_number.rb b/lib/plexus/arc_number.rb new file mode 100644 index 0000000..bb9d46f --- /dev/null +++ b/lib/plexus/arc_number.rb @@ -0,0 +1,50 @@ +module Plexus + # This module handles internal numbering of edges in order to differente between mutliple edges. + module ArcNumber + # Used to differentiate between mutli-edges + attr_accessor :number + + def initialize(p_source, p_target, p_number, p_label = nil) + self.number = p_number + super(p_source, p_target, p_label) + end + + # Returns (v,u) if self == (u,v). + def reverse + self.class.new(target, source, number, label) + end + + # Allow for hashing of self loops. + def hash + super ^ number.hash + end + + def to_s + super + "[#{number}]" + end + + def <=>(rhs) + (result = super(rhs)) == 0 ? number <=> rhs.number : result + end + + def inspect + "#{self.class.to_s}[#{source.inspect},#{target.inspect},#{number.inspect},#{label.inspect}]" + end + + def eql?(rhs) + super(rhs) and (rhs.number.nil? or number.nil? or number == rhs.number) + end + + def ==(rhs) + eql?(rhs) + end + + # Shortcut constructor. Instead of Arc.new(1,2) one can use Arc[1,2] + def self.included(cl) + # FIXME: lacks a cl.class_eval, no? + def cl.[](p_source, p_target, p_number = nil, p_label = nil) + new(p_source, p_target, p_number, p_label) + end + end + end +end diff --git a/lib/graphy/biconnected.rb b/lib/plexus/biconnected.rb similarity index 99% rename from lib/graphy/biconnected.rb rename to lib/plexus/biconnected.rb index c7d0f12..278e88e 100644 --- a/lib/graphy/biconnected.rb +++ b/lib/plexus/biconnected.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus # Biconnected is a module for adding the biconnected algorithm to # UndirectedGraphs @@ -81,4 +81,4 @@ def biconnected end # biconnected end # Biconnected -end # Graphy +end # Plexus diff --git a/lib/graphy/chinese_postman.rb b/lib/plexus/chinese_postman.rb similarity index 98% rename from lib/graphy/chinese_postman.rb rename to lib/plexus/chinese_postman.rb index e5cb866..ce34d10 100644 --- a/lib/graphy/chinese_postman.rb +++ b/lib/plexus/chinese_postman.rb @@ -1,5 +1,4 @@ -module Graphy - +module Plexus module ChinesePostman # Returns the shortest walk that traverses all arcs at least @@ -13,8 +12,8 @@ def closed_chinese_postman_tour(start, weight=nil, zero=0) cp_euler_circuit(start, f, path) end - private - + private + def cp_euler_circuit(start, f, path) # :nodoc: circuit = [u=v=start] bridge_taken = Hash.new {|h,k| h[k] = Hash.new} @@ -32,7 +31,7 @@ def cp_euler_circuit(start, f, path) # :nodoc: u=v end; circuit end - + def cp_cancel_cycle(cost, path, f, start, zero) # :nodoc: u = start; k = nil begin @@ -46,7 +45,7 @@ def cp_cancel_cycle(cost, path, f, start, zero) # :nodoc: end until (u=v) != start true # This routine always returns true to make cp_improve easier end - + def cp_improve(f, positive, negative, cost, zero) # :nodoc: residual = self.class.new negative.each do |u| @@ -59,7 +58,7 @@ def cp_improve(f, positive, negative, cost, zero) # :nodoc: i = residual.vertices.detect {|v| r_cost[v][v] and r_cost[v][v] < zero} i ? cp_cancel_cycle(r_cost, r_path, f, i) : false end - + def cp_find_feasible(delta, positive, negative, zero) # :nodoc: f = Hash.new {|h,k| h[k] = Hash.new} negative.each do |i| @@ -70,7 +69,7 @@ def cp_find_feasible(delta, positive, negative, zero) # :nodoc: end end; f end - + def cp_valid_least_cost?(c, zero) # :nodoc: vertices.each do |i| vertices.each do |j| @@ -78,7 +77,7 @@ def cp_valid_least_cost?(c, zero) # :nodoc: end end; true end - + def cp_unbalanced(delta) # :nodoc: negative = []; positive = [] vertices.each do |v| @@ -88,5 +87,5 @@ def cp_unbalanced(delta) # :nodoc: end end # Chinese Postman -end # Graphy - +end # Plexus + diff --git a/lib/plexus/classes/graph_classes.rb b/lib/plexus/classes/graph_classes.rb new file mode 100644 index 0000000..fc0218c --- /dev/null +++ b/lib/plexus/classes/graph_classes.rb @@ -0,0 +1,28 @@ +module Plexus + # A generic {GraphBuilder Graph} class you can inherit from. + class Graph; include GraphBuilder; end + + # A generic {AdjacencyGraphBuilder AdjacencyGraph} class you can inherit from. + class AdjacencyGraph < Graph; include AdjacencyGraphBuilder; end + + # A generic {DirectedGraphBuilder DirectedGraph} class you can inherit from. + class DirectedGraph < Graph; include DirectedGraphBuilder; end + + # A generic {DigraphBuilder Digraph} class you can inherit from. + class Digraph < Graph; include DigraphBuilder; end + + # A generic {DirectedPseudoGraphBuilder DirectedPseudoGraph} class you can inherit from. + class DirectedPseudoGraph < Graph; include DirectedPseudoGraphBuilder; end + + # A generic {DirectedMultiGraphBuilder DirectedMultiGraph} class you can inherit from. + class DirectedMultiGraph < Graph; include DirectedMultiGraphBuilder; end + + # A generic {UndirectedGraphBuilder UndirectedGraph} class you can inherit from. + class UndirectedGraph < Graph; include UndirectedGraphBuilder; end + + # A generic {UndirectedPseudoGraphBuilder UndirectedPseudoGraph} class you can inherit from. + class UndirectedPseudoGraph < Graph; include UndirectedPseudoGraphBuilder; end + + # A generic {UndirectedMultiGraphBuilder UndirectedMultiGraph} class you can inherit from. + class UndirectedMultiGraph < Graph; include UndirectedMultiGraphBuilder; end +end diff --git a/lib/plexus/common.rb b/lib/plexus/common.rb new file mode 100644 index 0000000..7ede4c6 --- /dev/null +++ b/lib/plexus/common.rb @@ -0,0 +1,63 @@ +module Plexus + + # This class defines a cycle graph of size n. + # This is easily done by using the base Graph + # class and implemeting the minimum methods needed to + # make it work. This is a good example to look + # at for making one's own graph classes. + module CycleBuilder + def initialize(n) + @size = n; + end + + def directed? + false + end + + def vertices + (1..@size).to_a + end + + def vertex?(v) + v > 0 and v <= @size + end + + def edge?(u,v = nil) + u, v = [u.source, v.target] if u.is_a? Plexus::Arc + vertex?(u) && vertex?(v) && ((v-u == 1) or (u == @size && v = 1)) + end + + def edges + Array.new(@size) { |i| Plexus::Edge[i+1, (i+1) == @size ? 1 : i+2]} + end + end # CycleBuilder + + # This class defines a complete graph of size n. + # This is easily done by using the base Graph + # class and implemeting the minimum methods needed to + # make it work. This is a good example to look + # at for making one's own graph classes. + module CompleteBuilder + include CycleBuilder + + def initialize(n) + @size = n + @edges = nil + end + + def edges + return @edges if @edges # cache edges + @edges = [] + @size.times do |u| + @size.times { |v| @edges << Plexus::Edge[u+1, v+1]} + end + @edges + end + + def edge?(u, v = nil) + u, v = [u.source, v.target] if u.kind_of? Plexus::Arc + vertex?(u) && vertex?(v) + end + end # CompleteBuilder + +end # Plexus diff --git a/lib/graphy/comparability.rb b/lib/plexus/comparability.rb similarity index 79% rename from lib/graphy/comparability.rb rename to lib/plexus/comparability.rb index 5f8b9ec..41a038e 100644 --- a/lib/graphy/comparability.rb +++ b/lib/plexus/comparability.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus module Comparability # A comparability graph is an UndirectedGraph that has a transitive @@ -19,7 +19,7 @@ def gamma_decomposition if classification[e].nil? k += 1 classification[e] = k; classification[e.reverse] = -k - comparability &&= graphy_comparability_explore(e, k, classification) + comparability &&= plexus_comparability_explore(e, k, classification) end end; [classification, comparability] end @@ -34,12 +34,12 @@ def transitive_orientation(digraph_class=Digraph) # Taken from Figure 5.10, on pg. 130 of Martin Golumbic's, _Algorithmic_Graph_ # _Theory_and_Perfect_Graphs. - def graphy_comparability_explore(edge, k, classification, space='') - ret = graphy_comparability_explore_inner(edge, k, classification, :forward, space) - graphy_comparability_explore_inner(edge.reverse, k, classification, :backward, space) && ret + def plexus_comparability_explore(edge, k, classification, space='') + ret = plexus_comparability_explore_inner(edge, k, classification, :forward, space) + plexus_comparability_explore_inner(edge.reverse, k, classification, :backward, space) && ret end - def graphy_comparability_explore_inner(edge, k, classification, direction,space) + def plexus_comparability_explore_inner(edge, k, classification, direction,space) comparability = true adj_target = adjacent(edge[1]) adjacent(edge[0]).select do |mt| @@ -50,14 +50,14 @@ def graphy_comparability_explore_inner(edge, k, classification, direction,space) if classification[e].nil? classification[e] = k classification[e.reverse] = -k - comparability = graphy_comparability_explore(e, k, classification, ' '+space) && comparability + comparability = plexus_comparability_explore(e, k, classification, ' '+space) && comparability elsif classification[e] == -k classification[e] = k - graphy_comparability_explore(e, k, classification, ' '+space) + plexus_comparability_explore(e, k, classification, ' '+space) comparability = false end end; comparability - end # graphy_comparability_explore_inner + end # plexus_comparability_explore_inner end # Comparability -end # Graphy +end # Plexus diff --git a/lib/plexus/directed_graph.rb b/lib/plexus/directed_graph.rb new file mode 100644 index 0000000..1664b33 --- /dev/null +++ b/lib/plexus/directed_graph.rb @@ -0,0 +1,78 @@ +module Plexus + + # This implements a directed graph which does not allow parallel + # edges nor loops. That is, only one arc per nodes couple, + # and only one parent per node. Mimics the typical hierarchy + # structure. + module DirectedGraphBuilder + include GraphBuilder + + autoload :Algorithms, "plexus/directed_graph/algorithms" + autoload :Distance, "plexus/directed_graph/distance" + + # FIXME: DRY this snippet, I didn't find a clever way to + # to dit though + # TODO: well, extends_host_with do ... end would be cool, + # using Module.new.module_eval(&block) in the helper. + extends_host + + module ClassMethods + def [](*a) + self.new.from_array(*a) + end + end + + def initialize(*params) + # FIXME/TODO: setting args to the hash or {} while getting rid + # on the previous parameters prevents from passing another + # graph to the initializer, so you cannot do things like: + # UndirectedGraph.new(Digraph[1,2, 2,3, 2,4, 4,5, 6,4, 1,6]) + # As args must be a hash, if we're to allow such syntax, + # we should provide a way to handle the graph as a hash + # member. + args = (params.pop if params.last.kind_of? Hash) || {} + args[:algorithmic_category] = DirectedGraphBuilder::Algorithms + super *(params << args) + end + end + + # DirectedGraph is just an alias for Digraph should one desire + DigraphBuilder = DirectedGraphBuilder + + # This is a Digraph that allows for parallel edges, but does not + # allow loops. + module DirectedPseudoGraphBuilder + include DirectedGraphBuilder + extends_host + + module ClassMethods + def [](*a) + self.new.from_array(*a) + end + end + + def initialize(*params) + args = (params.pop if params.last.kind_of? Hash) || {} + args[:parallel_edges] = true + super *(params << args) + end + end + + # This is a Digraph that allows for both parallel edges and loops. + module DirectedMultiGraphBuilder + include DirectedPseudoGraphBuilder + extends_host + + module ClassMethods + def [](*a) + self.new.from_array(*a) + end + end + + def initialize(*params) + args = (params.pop if params.last.kind_of? Hash) || {} + args[:loops] = true + super *(params << args) + end + end +end diff --git a/lib/plexus/directed_graph/algorithms.rb b/lib/plexus/directed_graph/algorithms.rb new file mode 100644 index 0000000..b874cd3 --- /dev/null +++ b/lib/plexus/directed_graph/algorithms.rb @@ -0,0 +1,95 @@ +module Plexus + # Digraph is a directed graph which is a finite set of vertices + # and a finite set of edges connecting vertices. It cannot contain parallel + # edges going from the same source vertex to the same target. It also + # cannot contain loops, i.e. edges that go have the same vertex for source + # and target. + # + # DirectedPseudoGraph is a class that allows for parallel edges, and + # DirectedMultiGraph is a class that allows for parallel edges and loops + # as well. + module DirectedGraphBuilder + module Algorithms + include Search + include StrongComponents + include Distance + include ChinesePostman + + # A directed graph is directed by definition. + # + # @return [Boolean] always true + # + def directed? + true + end + + # A digraph uses the Arc class for edges. + # + # @return [Plexus::MultiArc, Plexus::Arc] `Plexus::MultiArc` if the graph allows for parallel edges, + # `Plexus::Arc` otherwise. + # + def edge_class + @parallel_edges ? Plexus::MultiArc : Plexus::Arc + end + + # Reverse all edges in a graph. + # + # @return [DirectedGraph] a copy of the receiver for which the direction of edges has + # been inverted. + # + def reversal + result = self.class.new + edges.inject(result) { |a,e| a << e.reverse} + vertices.each { |v| result.add_vertex!(v) unless result.vertex?(v) } + result + end + + # Check whether the graph is oriented or not. + # + # @return [Boolean] + # + def oriented? + e = edges + re = e.map { |x| x.reverse} + not e.any? { |x| re.include?(x)} + end + + # Balanced is the state when the out edges count is equal to the in edges count. + # + # @return [Boolean] + # + def balanced?(v) + out_degree(v) == in_degree(v) + end + + # Returns out_degree(v) - in_degree(v). + # + def delta(v) + out_degree(v) - in_degree(v) + end + + def community(node, direction, options = {:recursive => true}) + nodes, stack = {}, adjacent(node, :direction => direction) + while n = stack.pop + unless nodes[n.object_id] || node == n + nodes[n.object_id] = n + stack += adjacent(n, :direction => direction) if options[:recursive] + end + end + nodes.values + end + + def descendants(node, options = {:recursive => true}) + community(node, :out) + end + + def ancestors(node, options = {:recursive => true}) + community(node, :in) + end + + def family(node, options = {:recursive => true}) + community(node, :all) + end + end + end +end diff --git a/lib/plexus/directed_graph/distance.rb b/lib/plexus/directed_graph/distance.rb new file mode 100644 index 0000000..73b9f28 --- /dev/null +++ b/lib/plexus/directed_graph/distance.rb @@ -0,0 +1,167 @@ +module Plexus + module DirectedGraphBuilder + + # This module provides algorithms computing distance between + # vertices. + module Distance + + # Shortest path computation. + # + # From: Jorgen Band-Jensen and Gregory Gutin, + # [*Digraphs: Theory, Algorithms and Applications*](http://www.springer.com/mathematics/numbers/book/978-1-84800-997-4), pg. 53-54. + # Complexity `O(n+m)`. + # + # Requires the graph to be acyclic. If the graph is not acyclic, + # then see {Distance#dijkstras_algorithm} or {Distance#bellman_ford_moore} + # for possible solutions. + # + # @param [vertex] start the starting vertex + # @param [Proc, #[]] weight can be a `Proc`, or anything else accessed using the `[]` + # operator. If not a `Proc`, and if no label accessible through `[]`, it will + # default to using the value stored in the label for the {Arc}. If a `Proc`, it will + # pass the edge to the proc and use the resulting value. + # @param [Integer] zero used for math systems with a different definition of zero + # + # @return [Hash] a hash with the key being a vertex and the value being the + # distance. A missing vertex from the hash is equivalent to an infinite distance. + def shortest_path(start, weight = nil, zero = 0) + dist = { start => zero } + path = {} + topsort(start) do |vi| + next if vi == start + dist[vi], path[vi] = adjacent(vi, :direction => :in).map do |vj| + [dist[vj] + cost(vj,vi,weight), vj] + end.min { |a,b| a[0] <=> b[0]} + end; + dist.keys.size == vertices.size ? [dist, path] : nil + end + + # Finds the distance from a given vertex in a weighted digraph + # to the rest of the vertices, provided all the weights of arcs + # are non-negative. + # + # If negative arcs exist in the graph, two basic options exist: + # + # * modify all weights to be positive using an offset (temporary at least) + # * use the {Distance#bellman_ford_moore} algorithm. + # + # Also, if the graph is acyclic, use the {Distance#shortest_path algorithm}. + # + # From: Jorgen Band-Jensen and Gregory Gutin, + # [*Digraphs: Theory, Algorithms and Applications*](http://www.springer.com/mathematics/numbers/book/978-1-84800-997-4), pg. 53-54. + # + # Complexity `O(n*log(n) + m)`. + # + # @param [vertex] s + # @param [Proc, #[]] weight can be a `Proc`, or anything else accessed using the `[]` + # operator. If not a `Proc`, and if no label accessible through `[]`, it will + # default to using the value stored in the label for the {Arc}. If a `Proc`, it will + # pass the edge to the proc and use the resulting value. + # @param [Integer] zero used for math systems with a different definition of zero + # @return [Hash] a hash with the key being a vertex and the value being the + # distance. A missing vertex from the hash is equivalent to an infinite distance. + def dijkstras_algorithm(s, weight = nil, zero = 0) + q = vertices; distance = { s => zero } + path = {} + while not q.empty? + v = (q & distance.keys).inject(nil) { |a,k| (!a.nil?) && (distance[a] < distance[k]) ? a : k} + q.delete(v) + (q & adjacent(v)).each do |u| + c = cost(v, u, weight) + if distance[u].nil? or distance[u] > (c + distance[v]) + distance[u] = c + distance[v] + path[u] = v + end + end + end + [distance, path] + end + + # Finds the distances from a given vertex in a weighted digraph + # to the rest of the vertices, provided the graph has no negative cycle. + # + # If no negative weights exist, then {Distance#dijkstras_algorithm} is more + # efficient in time and space. Also, if the graph is acyclic, use the + # {Distance#shortest_path} algorithm. + # + # From: Jorgen Band-Jensen and Gregory Gutin, + # [*Digraphs: Theory, Algorithms and Applications*](http://www.springer.com/mathematics/numbers/book/978-1-84800-997-4), pg. 56-58.. + # + # Complexity `O(nm)`. + # + # @param [vertex] s + # @param [Proc, #[]] weight can be a `Proc`, or anything else accessed using the `[]` + # operator. If not a `Proc`, and if no label accessible through `[]`, it will + # default to using the value stored in the label for the {Arc}. If a `Proc`, it will + # pass the edge to the proc and use the resulting value. + # @param [Integer] zero used for math systems with a different definition of zero + # @return [Hash] a hash with the key being a vertex and the value being the + # distance. A missing vertex from the hash is equivalent to an infinite distance. + def bellman_ford_moore(start, weight = nil, zero = 0) + distance = { start => zero } + path = {} + 2.upto(vertices.size) do + edges.each do |e| + u, v = e[0], e[1] + unless distance[u].nil? + c = cost(u, v, weight) + distance[u] + if distance[v].nil? or c < distance[v] + distance[v] = c + path[v] = u + end + end + end + end + [distance, path] + end + + # Uses the Floyd-Warshall algorithm to efficiently find + # and record shortest paths while establishing at the same time + # the costs for all vertices in a graph. + # + # See S.Skiena, *The Algorithm Design Manual*, Springer Verlag, 1998 for more details. + # + # O(n^3) complexity in time. + # + # @param [Proc, nil] weight specifies how an edge weight is determined. + # If it's a `Proc`, the {Arc} is passed to it; if it's `nil`, it will just use + # the value in the label for the Arc; otherwise the weight is + # determined by applying the `[]` operator to the value in the + # label for the {Arc}. + # @param [Integer] zero defines the zero value in the math system used. + # This allows for no assumptions to be made about the math system and + # fully functional duck typing. + # @return [Array(matrice, matrice, Hash)] a pair of matrices and a hash of delta values. + # The matrices will be indexed by two vertices and are implemented as a Hash of Hashes. + # The first matrix is the cost, the second matrix is the shortest path spanning tree. + # The delta (difference of number of in-edges and out-edges) is indexed by vertex. + def floyd_warshall(weight = nil, zero = 0) + c = Hash.new { |h,k| h[k] = Hash.new } + path = Hash.new { |h,k| h[k] = Hash.new } + delta = Hash.new { |h,k| h[k] = 0 } + edges.each do |e| + delta[e.source] += 1 + delta[e.target] -= 1 + path[e.source][e.target] = e.target + c[e.source][e.target] = cost(e, weight) + end + vertices.each do |k| + vertices.each do |i| + if c[i][k] + vertices.each do |j| + if c[k][j] && + (c[i][j].nil? or c[i][j] > (c[i][k] + c[k][j])) + path[i][j] = path[i][k] + c[i][j] = c[i][k] + c[k][j] + return nil if i == j and c[i][j] < zero + end + end + end + end + end + [c, path, delta] + end + + end # Distance + end # DirectedGraph +end # Plexus diff --git a/lib/plexus/dot.rb b/lib/plexus/dot.rb new file mode 100644 index 0000000..28770d8 --- /dev/null +++ b/lib/plexus/dot.rb @@ -0,0 +1,94 @@ +module Plexus + module Dot + + #FIXME: don't really understood where we stand with the dot generators. + # RDoc ships with a dot.rb which seems pretty efficient. + # Are these helpers still needed, and if not, how should we replace them? + + # Creates a DOT::DOTDigraph for directed graphs or a DOT::DOTSubgraph for + # undirected graphs. + # + # @param [Hash] params can contain any graph property specified in + # rdot.rb. If an edge or vertex label is a kind of Hash, then the keys + # which match dot properties will be used as well. + # @return [DOT::DOTDigraph, DOT::DOTSubgraph] + def to_dot_graph(params = {}) + params['name'] ||= self.class.name.gsub(/:/, '_') + fontsize = params['fontsize'] ? params['fontsize'] : '8' + graph = (directed? ? DOT::DOTDigraph : DOT::DOTSubgraph).new(params) + edge_klass = directed? ? DOT::DOTDirectedArc : DOT::DOTArc + + vertices.each do |v| + name = v.to_s || v.__id__.to_s + name = name.dup.gsub(/"/, "'") + + params = { 'name' => '"'+ name +'"', + 'fontsize' => fontsize, + 'label' => name} + + v_label = vertex_label(v) + params.merge!(v_label) if v_label and v_label.kind_of? Hash + + graph << DOT::DOTNode.new(params) + end + + edges.each do |e| + if e.source.to_s.nil? + source_label = e.source.__id__.to_s + else + source_label = e.source.to_s.dup + end + + if e.target.to_s.nil? + target_label = e.target.__id__.to_s + else + target_label = e.target.to_s.dup + end + + source_label.gsub!(/"/, "'") + target_label.gsub!(/"/, "'") + + params = { 'from' => '"'+ source_label + '"', + 'to' => '"'+ target_label + '"', + 'fontsize' => fontsize } + + e_label = edge_label(e) + params.merge!(e_label) if e_label and e_label.kind_of? Hash + + graph << edge_klass.new(params) + end + + graph + end + + # Output the dot format as a string + def to_dot(params = {}) + to_dot_graph(params).to_s + end + + # Call +dotty+ for the graph which is written to the file 'graph.dot' + # in the # current directory. + def dotty(params = {}, dotfile = 'graph.dot') + File.open(dotfile, 'w') {|f| f << to_dot(params) } + system('dotty', dotfile) + end + + # Use +dot+ to create a graphical representation of the graph. Returns the + # filename of the graphics file. + def write_to_graphic_file(fmt = 'png', dotfile = 'graph') + src = dotfile + '.dot' + dot = dotfile + '.' + fmt + + # DOT::DOTSubgraph creates subgraphs, but that's broken. + buffer = self.to_dot + buffer.gsub!(/^subgraph/, "graph") + + File.open(src, 'w') {|f| f << buffer << "\n"} + system( "dot -T#{fmt} #{src} -o #{dot}" ) + + dot + end + alias as_dot_graphic write_to_graphic_file + + end # Dot +end # module Plexus diff --git a/lib/plexus/edge.rb b/lib/plexus/edge.rb new file mode 100644 index 0000000..8ec51fa --- /dev/null +++ b/lib/plexus/edge.rb @@ -0,0 +1,40 @@ +module Plexus + # An undirected edge is simply an undirected pair (source, target) used in + # undirected graphs. Edge[u,v] == Edge[v,u] + class Edge < Arc + + # Edge equality allows for the swapping of source and target. + # + def eql?(other) + same_class = self.class.ancestors.include?(other.class) || other.class.ancestors.include?(self.class) + super || (same_class && target == other.source && source == other.target) + end + alias == eql? + + # Hash is defined such that source and target can be reversed and the + # hash value will be the same + def hash + source.hash ^ target.hash + end + + # Sort support + def <=>(rhs) + [[source,target].max, [source,target].min] <=> [[rhs.source,rhs.target].max, [rhs.source,rhs.target].min] + end + + # Edge[1,2].to_s => "(1=2)" + # Edge[2,1].to_s => "(1=2)" + # Edge[2,1,'test'].to_s => "(1=2 test)" + def to_s + l = label ? " '#{label.to_s}'" : '' + s = source.to_s + t = target.to_s + "(#{[s,t].min}=#{[s,t].max}#{l})" + end + + end + + class MultiEdge < Edge + include ArcNumber + end +end diff --git a/lib/plexus/ext.rb b/lib/plexus/ext.rb new file mode 100644 index 0000000..4c42e43 --- /dev/null +++ b/lib/plexus/ext.rb @@ -0,0 +1,79 @@ +class Object + # Get the singleton class of the object. + # Depending on the object which requested its singleton class, + # a `module_eval` or a `class_eval` will be performed. + # Object of special constant type (`Fixnum`, `NilClass`, `TrueClass`, + # `FalseClass` and `Symbol`) return `nil` as they do not have a + # singleton class. + # + # @return the singleton class + def singleton_class + if self.respond_to? :module_eval + self.module_eval("class << self; self; end") + elsif self.respond_to? :instance_eval + begin + self.instance_eval("class << self; self; end") + rescue TypeError + nil + end + end + end + + # Check wether the object is of the specified kind. + # If the receiver has a singleton class, will also perform + # the check on its singleton class' ancestors, so as to catch + # any included modules for object instances. + # + # Example: + # + # class A; include Digraph; end + # a.singleton_class.ancestors + # # => [Plexus::DirectedGraph::Algorithms, ... + # Plexus::Labels, Enumerable, Object, Plexus, Kernel, BasicObject] + # a.is_a? Plexus::Graph + # # => true + # + # @param [Class] klass + # @return [Boolean] + def is_a? klass + sc = self.singleton_class + if not sc.nil? + self.singleton_class.ancestors.include?(klass) || super + else + super + end + end +end + +class Module + # Helper which purpose is, given a class including a module, + # to make each methods defined within a module's submodule `ClassMethods` + # available as class methods to the receiving class. + # + # Example: + # + # module A + # extends_host + # module ClassMethods + # def selfy; puts "class method for #{self}"; end + # end + # end + # + # class B; include A; end + # + # B.selfy + # # => class method for B + # + # @option *params [Symbol] :with (:ClassMethods) the name of the + # module to extend the receiver with + def extends_host(*params) + args = (params.pop if params.last.is_a? Hash) || {} + @_extension_module = args[:with] || :ClassMethods + + def included(base) + unless @_extension_module.nil? + base.extend(self.const_get(@_extension_module)) + end + end + end +end diff --git a/lib/plexus/graph.rb b/lib/plexus/graph.rb new file mode 100644 index 0000000..ba4edae --- /dev/null +++ b/lib/plexus/graph.rb @@ -0,0 +1,626 @@ +module Plexus + # Using only a basic methods set, it implements all the *basic* functions + # of a graph. The process is under the control of the pattern + # {AdjacencyGraphBuilder}, unless a specific implementation is specified + # during initialization. + # + # An actual, complete implementation still needs to be done using this cheap result, + # hence {Digraph}, {UndirectedGraph} and their roomates. + module GraphBuilder + include Enumerable + include Labels + include Dot + + #def self.[](*a) + #puts self + #self.new.from_array(*a) + #end + # after the class->module transition, has been moved at implementation level, + # using a helper (extends_host) + # + extends_host + module ClassMethods + def [](*a) + self.new.from_array(*a) + end + end + + # Creates a generic graph. + # + # @param [Hash(Plexus::Graph, Array)] *params initialization parameters. + # See {AdjacencyGraphBuilder#implementation_initialize} for more details. + # @return [Graph] + # + def initialize(*params) + raise ArgumentError if params.any? do |p| + # FIXME: checking wether it's a GraphBuilder (module) is not sufficient + # and the is_a? redefinition trick (instance_evaling) should be + # completed by a clever way to check the actual class of p. + # Maybe using ObjectSpace to get the available Graph classes? + !(p.is_a? Plexus::GraphBuilder or p.is_a? Array or p.is_a? Hash) + end + + args = params.last || {} + + class << self + self + end.module_eval do + # These inclusions trigger some validations checks by the way. + include(args[:implementation] ? args[:implementation] : Plexus::AdjacencyGraphBuilder) + include(args[:algorithmic_category] ? args[:algorithmic_category] : Plexus::DigraphBuilder ) + end + + implementation_initialize(*params) + end + + # Shortcut for creating a Graph. + # + # Using an arry of implicit {Arc}, specifying the vertices: + # + # Plexus::Graph[1,2, 2,3, 2,4, 4,5].edges.to_a.to_s + # # => "(1-2)(2-3)(2-4)(4-5)" + # + # Using a Hash for specifying labels along the way: + # + # Plexus::Graph[ [:a,:b] => 3, [:b,:c] => 4 ] (note: do not use for Multi or Pseudo graphs) + # + # @param [Array, Hash] *a + # @return [Graph] + def from_array(*a) + if a.size == 1 and a[0].is_a? Hash + # Convert to edge class + a[0].each do |k,v| + #FIXME, edge class shouldn't be assume here!!! + if edge_class.include? Plexus::ArcNumber + add_edge!(edge_class[k[0],k[1],nil,v]) + else + add_edge!(edge_class[k[0],k[1],v]) + end + end + #FIXME, edge class shouldn't be assume here!!! + elsif a[0].is_a? Plexus::Arc + a.each{ |e| add_edge!(e); self[e] = e.label} + elsif a.size % 2 == 0 + 0.step(a.size-1, 2) {|i| add_edge!(a[i], a[i+1])} + else + raise ArgumentError + end + self + end + + # Non destructive version of {AdjacencyGraphBuilder#add_vertex!} (works on a copy of the graph). + # + # @param [vertex] v + # @param [Label] l + # @return [Graph] a new graph with the supplementary vertex + def add_vertex(v, l = nil) + x = self.class.new(self) + x.add_vertex!(v, l) + end + + # Non destructive version {AdjacencyGraphBuilder#add_edge!} (works on a copy of the graph). + # + # @param [vertex] u + # @param [vertex] v + # @param [Label] l + # @return [Graph] a new graph with the supplementary edge + def add_edge(u, v = nil, l = nil) + x = self.class.new(self) + x.add_edge!(u, v, l) + end + alias add_arc add_edge + + # Non destructive version of {AdjacencyGraphBuilder#remove_vertex!} (works on a copy of the graph). + # + # @param [vertex] v + # @return [Graph] a new graph without the specified vertex + def remove_vertex(v) + x = self.class.new(self) + x.remove_vertex!(v) + end + + # Non destructive version {AdjacencyGraphBuilder#remove_edge!} (works on a copy of the graph). + # + # @param [vertex] u + # @param [vertex] v + # @return [Graph] a new graph without the specified edge + def remove_edge(u, v = nil) + x = self.class.new(self) + x.remove_edge!(u, v) + end + alias remove_arc remove_edge + + # Computes the adjacent portions of the Graph. + # + # The options specify the parameters about the adjacency search. + # Note: it is probably more efficently done in the implementation class. + # + # @param [vertex, Edge] x can either be a vertex an edge + # @option options [Symbol] :type (:vertices) can be either `:edges` or `:vertices` + # @option options [Symbol] :direction (:all) can be `:in`, `:out` or `:all` + # @return [Array] an array of the adjacent portions + # @fixme + def adjacent(x, options = {}) + d = directed? ? (options[:direction] || :out) : :all + + # Discharge the easy ones first. + return [x.source] if x.is_a? Arc and options[:type] == :vertices and d == :in + return [x.target] if x.is_a? Arc and options[:type] == :vertices and d == :out + return [x.source, x.target] if x.is_a? Arc and options[:type] != :edges and d == :all + + (options[:type] == :edges ? edges : to_a).select { |u| adjacent?(x,u,d) } + end + #FIXME: This is a hack around a serious problem + alias graph_adjacent adjacent + + # Adds all specified vertices to the vertex set. + # + # @param [#each] *a an Enumerable vertices set + # @return [Graph] `self` + def add_vertices!(*a) + a.each { |v| add_vertex! v } + self + end + + # Same as {GraphBuilder#add_vertices! add_vertices!} but works on copy of the receiver. + # + # @param [#each] *a + # @return [Graph] a modified copy of `self` + def add_vertices(*a) + x = self.class.new(self) + x.add_vertices!(*a) + self + end + + # Adds all edges mentionned in the specified Enumerable to the edge set. + # + # Elements of the Enumerable can be either two-element arrays or instances of + # {Edge} or {Arc}. + # + # @param [#each] *a an Enumerable edges set + # @return [Graph] `self` + def add_edges!(*a) + a.each { |edge| add_edge!(edge) } + self + end + alias add_arcs! add_edges! + + # Same as {GraphBuilder#add_egdes! add_edges!} but works on a copy of the receiver. + # + # @param [#each] *a an Enumerable edges set + # @return [Graph] a modified copy of `self` + def add_edges(*a) + x = self.class.new(self) + x.add_edges!(*a) + self + end + alias add_arcs add_edges + + # Removes all vertices mentionned in the specified Enumerable from the graph. + # + # The process relies on {GraphBuilder#remove_vertex! remove_vertex!}. + # + # @param [#each] *a an Enumerable vertices set + # @return [Graph] `self` + def remove_vertices!(*a) + a.each { |v| remove_vertex! v } + end + alias delete_vertices! remove_vertices! + + # Same as {GraphBuilder#remove_vertices! remove_vertices!} but works on a copy of the receiver. + # + # @param [#each] *a a vertex Enumerable set + # @return [Graph] a modified copy of `self` + def remove_vertices(*a) + x = self.class.new(self) + x.remove_vertices(*a) + end + alias delete_vertices remove_vertices + + # Removes all edges mentionned in the specified Enumerable from the graph. + # + # The process relies on {GraphBuilder#remove_edges! remove_edges!}. + # + # @param [#each] *a an Enumerable edges set + # @return [Graph] `self` + def remove_edges!(*a) + a.each { |e| remove_edge! e } + end + alias remove_arcs! remove_edges! + alias delete_edges! remove_edges! + alias delete_arcs! remove_edges! + + # Same as {GraphBuilder#remove_edges! remove_edges!} but works on a copy of the receiver. + # + # @param [#each] *a an Enumerable edges set + # @return [Graph] a modified copy of `self` + def remove_edges(*a) + x = self.class.new(self) + x.remove_edges!(*a) + end + alias remove_arcs remove_edges + alias delete_edges remove_edges + alias delete_arcs remove_edges + + # Executes the given block for each vertex. It allows for mixing Enumerable in. + def each(&block) + vertices.each(&block) + end + + # Returns true if the specified vertex belongs to the graph. + # + # This is a default implementation that is of O(n) average complexity. + # If a subclass uses a hash to store vertices, then this can be + # made into an O(1) average complexity operation. + # + # @param [vertex] v + # @return [Boolean] + def vertex?(v) + vertices.include?(v) + end + alias has_vertex? vertex? + # TODO: (has_)vertices? + + # Returns true if u or (u,v) is an {Edge edge} of the graph. + # + # @overload edge?(a) + # @param [Arc, Edge] a + # @overload edge?(u, v) + # @param [vertex] u + # @param [vertex] v + # @return [Boolean] + def edge?(*args) + edges.include?(edge_convert(*args)) + end + alias arc? edge? + alias has_edge? edge? + alias has_arc? edge? + + # Tests two objects to see if they are adjacent. + # + # Note that in this method, one is primarily concerned with finding + # all adjacent objects in a graph to a given object. The concern is primarily on seeing + # if two objects touch. For two vertexes, any edge between the two will usually do, but + # the direction can be specified if needed. + # + # @param [vertex] source + # @param [vertex] target + # @param [Symbol] direction (:all) constraint on the direction of adjacency; may be either `:in`, `:out` or `:all` + def adjacent?(source, target, direction = :all) + if source.is_a? Plexus::Arc + raise NoArcError unless edge? source + if target.is_a? Plexus::Arc + raise NoArcError unless edge? target + (direction != :out and source.source == target.target) or (direction != :in and source.target == target.source) + else + raise NoVertexError unless vertex? target + (direction != :out and source.source == target) or (direction != :in and source.target == target) + end + else + raise NoVertexError unless vertex? source + if target.is_a? Plexus::Arc + raise NoArcError unless edge? target + (direction != :out and source == target.target) or (direction != :in and source == target.source) + else + raise NoVertexError unless vertex? target + (direction != :out and edge?(target,source)) or (direction != :in and edge?(source,target)) + end + end + end + + # Is the graph connected? + # + # A graph is called connected if every pair of distinct vertices in the graph + # can be connected through some path. The exact definition depends on whether + # the graph is directed or not, hence this method should overriden in specific + # implementations. + # + # This methods implements a lazy routine using the internal vertices hash. + # If you ever want to check connectivity state using a bfs/dfs algorithm, use + # the `:algo => :bfs` or `:dfs` option. + # + # @return [Boolean] `true` if the graph is connected, `false` otherwise + def connected?(options = {}) + options = options.reverse_merge! :algo => :bfs + if options[:algo] == (:bfs || :dfs) + num_nodes = 0 + send(options[:algo]) { |n| num_nodes += 1 } + return num_nodes == @vertex_dict.size + else + !@vertex_dict.collect { |v| degree(v) > 0 }.any? { |check| check == false } + end + end + # TODO: do it! + # TODO: for directed graphs, add weakly_connected? and strongly_connected? (aliased as strong?) + # TODO: in the context of vertices/Arc, add connected_vertices? and disconnected_vertices? + # TODO: maybe implement some routine which would compute cuts and connectivity? tricky though, + # but would be useful (k_connected?(k)) + + # Returns true if the graph has no vertex. + # + # @return [Boolean] + def empty? + vertices.size.zero? + end + + # Returns true if the given object is a vertex or an {Arc arc} of the graph. + # + # @param [vertex, Arc] x + def include?(x) + x.is_a?(Plexus::Arc) ? edge?(x) : vertex?(x) + end + alias has? include? + + # Returns the neighborhood of the given vertex or {Arc arc}. + # + # This is equivalent to {GraphBuilder#adjacent adjacent}, but the type is based on the + # type of the specified object. + # + # @param [vertex, Arc] x + # @param [Symbol] direction (:all) can be either `:all`, `:in` or `:out` + def neighborhood(x, direction = :all) + adjacent(x, :direction => direction, :type => ((x.is_a? Plexus::Arc) ? :edges : :vertices )) + end + + # Union of all neighborhoods of vertices (or edges) in the Enumerable x minus the contents of x. + # + # Definition taken from: Jorgen Bang-Jensen, Gregory Gutin, *Digraphs: Theory, Algorithms and Applications*, pg. 4 + # + # @param [vertex] x + # @param [Symbol] direction can be either `:all`, `:in` or `:out` + def set_neighborhood(x, direction = :all) + x.inject(Set.new) { |a,v| a.merge(neighborhood(v, direction))}.reject { |v2| x.include?(v2) } + end + + # Union of all {GraphBuilder#set_neighborhood set_neighborhoods} reachable + # among the specified edges. + # + # Definition taken from Jorgen Bang-Jensen, Gregory Gutin, *Digraphs: + # Theory, Algorithms and Applications*, pg. 46 + # + # @param [vertex] w + # @param [Edges] p + # @param [Symbol] direction can be `:all`, `:in`, or `:out` + def closed_pth_neighborhood(w, p, direction = :all) + if p <= 0 + w + elsif p == 1 + (w + set_neighborhood(w, direction)).uniq + else + n = set_neighborhood(w, direction) + (w + n + closed_pth_neighborhood(n, p-1, direction)).uniq + end + end + + # Returns the neighboorhoods reachable in a certain amount of steps from + # every vertex (or edge) in the specified Enumerable. + # + # Definition taken from Jorgen Bang-Jensen, Gregory Gutin, _Digraphs: + # Theory, Algorithms and Applications_, pg. 46 + # + # @param [Enumerable] x + # @param [Integer] p number of steps to perform + # @param [Symbol] direction can be `:all`, `:in`, or `:out` + def open_pth_neighborhood(x, p, direction = :all) + if p <= 0 + x + elsif p == 1 + set_neighborhood(x,direction) + else + set_neighborhood(open_pth_neighborhood(x, p-1, direction), direction) - + closed_pth_neighborhood(x, p-1, direction) + end + end + + # Returns the number of out-edges (for directed graphs) or the number of + # incident edges (for undirected graphs) of the specified vertex. + # + # @param [vertex] v + # @return [Integer] number of matching edges + def out_degree(v) + adjacent(v, :direction => :out).size + end + + # Returns the number of in-edges (for directed graphs) or the number of + # incident edges (for undirected graphs) of the specified vertex + # + # @param [vertex] v + # @return [Integer] number of matching edges + def in_degree(v) + adjacent(v, :direction => :in).size + end + + # Returns the sum of the number in and out edges for the specified vertex. + # + # @param [vertex] v + # @return [Integer] degree + def degree(v) + in_degree(v) + out_degree(v) + end + + # Minimum in-degree of the graph. + # + # @return [Integer, nil] returns `nil` if the graph is empty + def min_in_degree + return nil if to_a.empty? + to_a.map { |v| in_degree(v) }.min + end + + # Minimum out-degree of the graph. + # + # @return [Integer, nil] returns `nil` if the graph is empty + def min_out_degree + return nil if to_a.empty? + to_a.map {|v| out_degree(v)}.min + end + + # Minimum degree of all vertexes of the graph. + # + # @return [Integer] `min` between {GraphBuilder#min_in_degree min_in_degree} + # and {GraphBuilder#min_out_degree max_out_degree} + def min_degree + [min_in_degree, min_out_degree].min + end + + # Maximum in-degree of the graph. + # + # @return [Integer, nil] returns `nil` if the graph is empty + def max_in_degree + return nil if to_a.empty? + vertices.map { |v| in_degree(v)}.max + end + + # Maximum out-degree of the graph. + # + # @return [Integer, nil] returns nil if the graph is empty + def max_out_degree + return nil if to_a.empty? + vertices.map { |v| out_degree(v)}.max + end + + # Maximum degree of all vertexes of the graph. + # + # @return [Integer] `max` between {GraphBuilder#max_in_degree max_in_degree} + # and {GraphBuilder#max_out_degree max_out_degree} + def max_degree + [max_in_degree, max_out_degree].max + end + + # Is the graph regular, that is are its min degree and max degree equal? + # + # @return [Boolean] + def regular? + min_degree == max_degree + end + + # Number of vertices. + # + # @return [Integer] + def size + vertices.size + end + alias num_vertices size + alias number_of_vertices size + + # Number of vertices. + # + # @return [Integer] + def num_vertices + vertices.size + end + alias number_of_vertices num_vertices + + # Number of edges. + # + # @return [Integer] + def num_edges + edges.size + end + alias number_of_edges num_edges + + # Utility method to show a string representation of the edges of the graph. + #def to_s + #edges.to_s + #end + + # Equality is defined to be same set of edges and directed? + def eql?(g) + return false unless g.is_a? Plexus::Graph + + (directed? == g.directed?) and + (vertices.sort == g.vertices.sort) and + (edges.sort == g.edges.sort) + end + alias == eql? + + # Merges another graph into the receiver. + # + # @param [Graph] other the graph to merge in + # @return [Graph] `self` + def merge(other) + other.vertices.each { |v| add_vertex!(v) } + other.edges.each { |e| add_edge!(e) } + other.edges.each { |e| add_edge!(e.reverse) } if directed? and !other.directed? + self + end + + # A synonym for {GraphBuilder#merge merge}, but doesn't modify the current graph. + # + # @param [Graph, Arc] other + # @return [Graph] a new graph + def +(other) + result = self.class.new(self) + case other + when Plexus::Graph + result.merge(other) + when Plexus::Arc + result.add_edge!(other) + else + result.add_vertex!(other) + end + end + + # Removes all vertices in the specified graph. + # + # @param [Graph, Arc] other + # @return [Graph] + def -(other) + case other + when Plexus::Graph + induced_subgraph(vertices - other.vertices) + when Plexus::Arc + self.class.new(self).remove_edge!(other) + else + self.class.new(self).remove_vertex!(other) + end + end + + # A synonym for {AdjacencyGraphBuilder#add_edge! add_edge!}. + def <<(edge) + add_edge!(edge) + end + + # Computes the complement of the current graph. + # + # @return [Graph] + def complement + vertices.inject(self.class.new) do |a,v| + a.add_vertex!(v) + vertices.each { |v2| a.add_edge!(v, v2) unless edge?(v, v2) }; a + end + end + + # Given an array of vertices, computes the induced subgraph. + # + # @param [Array(vertex)] v + # @return [Graph] + def induced_subgraph(v) + edges.inject(self.class.new) do |a,e| + (v.include?(e.source) and v.include?(e.target)) ? (a << e) : a + end + end + + def inspect + ## FIXME: broken, it's not updated. The issue's not with inspect, but it's worth mentionning here. + ## Example: + ## dg = Digraph[1,2, 2,3, 2,4, 4,5, 6,4, 1,6] + ## dg.add_vertices! 1, 5, "yosh" + ## # => Plexus::Digraph[Plexus::Arc[1,2,nil], Plexus::Arc[1,6,nil], Plexus::Arc[2,3,nil], Plexus::Arc[2,4,nil], Plexus::Arc[4,5,nil], Plexus::Arc[6,4,nil]] + ## dg.vertex?("yosh") + ## # => true + ## dg + ## # =>Plexus::Digraph[Plexus::Arc[1,2,nil], Plexus::Arc[1,6,nil], Plexus::Arc[2,3,nil], Plexus::Arc[2,4,nil], Plexus::Arc[4,5,nil], Plexus::Arc[6,4,nil]] + ## the new vertex doesn't show up. + ## Actually this version of inspect is far too verbose IMO :) + l = vertices.select { |v| self[v]}.map { |u| "vertex_label_set(#{u.inspect}, #{self[u].inspect})"}.join('.') + self.class.to_s + '[' + edges.map {|e| e.inspect}.join(', ') + ']' + (l && l != '' ? '.'+l : '') + end + + private + + # ? + def edge_convert(*args) + args[0].is_a?(Plexus::Arc) ? args[0] : edge_class[*args] + end + end +end diff --git a/lib/plexus/labels.rb b/lib/plexus/labels.rb new file mode 100644 index 0000000..dcb22db --- /dev/null +++ b/lib/plexus/labels.rb @@ -0,0 +1,112 @@ +module Plexus + # This module add support for labels. + # + # The graph labeling process consist in assigning labels, traditionally represented + # by integers, to the edges or vertices, or both, of a graph. Plexus recommands you + # abide by this rule and do use integers as labels. + # + # Some algorithms can make use of labeling (sea {Plexus::Search} for instance). + module Labels + + # Return a label for an edge or vertex. + def [](u) + (u.is_a? Plexus::Arc) ? edge_label(u) : vertex_label(u) + end + + # Set a label for an edge or vertex. + def []=(u, value) + (u.is_a? Plexus::Arc) ? edge_label_set(u, value) : vertex_label_set(u, value) + end + + # Delete a label entirely. + def delete_label(u) + (u.is_a? Plexus::Arc) ? edge_label_delete(u) : vertex_label_delete(u) + end + + # Get the label for an edge. + def vertex_label(v) + vertex_label_dict[v] + end + + # Set the label for an edge. + def vertex_label_set(v, l) + vertex_label_dict[v] = l + self + end + + # Get the label for an edge. + def edge_label(u, v = nil, n = nil) + u = edge_convert(u,v,n) + edge_label_dict[u] + end + + # Set the label for an edge. + def edge_label_set(u, v = nil, l = nil, n = nil) + u.is_a?(Plexus::Arc) ? l = v : u = edge_convert(u, v, n) + edge_label_dict[u] = l + self + end + + # Delete all graph labels. + def clear_all_labels + @vertex_labels = {} + @edge_labels = {} + end + + # Delete an edge label. + def edge_label_delete(u, v = nil, n = nil) + u = edge_convert(u, v, n) + edge_label_dict.delete(u) + end + + # Delete a vertex label. + def vertex_label_delete(v) + vertex_label_dict.delete(v) + end + + protected + + def vertex_label_dict + @vertex_labels ||= {} + end + + def edge_label_dict + @edge_labels ||= {} + end + + # A generic cost function. + # + # It either calls the `weight` function with an edge constructed from the + # two specified nodes, or calls the `[]` operator of the label when given + # a single value. + # + # If no weight value is specified, the label itself is treated as the cost value. + # + # Note: This function will not work for Pseudo or Multi graphs at present. + # FIXME: Remove u,v interface to fix Pseudo Multi graph problems. + def cost(u, v = nil, weight = nil) + u.is_a?(Arc) ? weight = v : u = edge_class[u,v] + case weight + when Proc + weight.call(u) + when nil + self[u] + else + self[u][weight] + end + end + alias property cost # makes sense for property retrieval in general + + # A function to set properties specified by the user. + def property_set(u, name, value) + case name + when Proc + name.call(value) + when nil + self[u] = value + else + self[u][name] = value + end + end + end +end diff --git a/lib/graphy/maximum_flow.rb b/lib/plexus/maximum_flow.rb similarity index 84% rename from lib/graphy/maximum_flow.rb rename to lib/plexus/maximum_flow.rb index 63d230e..242fdbb 100644 --- a/lib/graphy/maximum_flow.rb +++ b/lib/plexus/maximum_flow.rb @@ -1,6 +1,7 @@ -module Graphy +module Plexus + class Network + include DigraphBuilder - class Network < Digraph attr_accessor :lower, :upper, :cost, :flow def residual(residual_capacity, cost_property, zero = 0) @@ -16,15 +17,15 @@ def residual(residual_capacity, cost_property, zero = 0) end r end - + def maximum_flow() eliminate_lower_bounds.maximum_flow_prime.restore_lower_bounds(self); end - - private: + + private def eliminate_lower_bounds no_lower_bounds = Digraph.new(self) if self.upper.kind_of? Proc then - no_lower_bounds.upper = Proc.new {|e| self.upper.call(e) - property(e,self.lower) } + no_lower_bounds.upper = Proc.new {|e| self.upper.call(e) - property(e,self.lower) } else no_lower_bounds.edges.each {|e| no_lower_bounds[e][self.upper] -= property(e,self.lower)} end @@ -32,19 +33,18 @@ def eliminate_lower_bounds end def restore_lower_bounds(src) - src.edges.each do {|e| (src.flow ? src[e][src.flow] : src[e]) = property(e,self.flow) + src.property(e,self.lower) } + src.edges.each do |e| + (src.flow ? src[e][src.flow] : src[e]) = property(e, self.flow) + src.property(e, self.lower) + end src end - + def maximum_flow_prime end - - end - - module Graph + end # Network + module GraphBuilder module MaximumFlow - # Maximum flow, it returns an array with the maximum flow and a hash of flow per edge # Currently a highly inefficient implementation, FIXME, This should use Goldberg and Tarjan's method. def maximum_flow(s, t, capacity = nil, zero = 0) @@ -68,10 +68,10 @@ def maximum_flow(s, t, capacity = nil, zero = 0) end total += amt end - + [total, flow] end end # MaximumFlow - end # Graph -end # Graphy + end # GraphBuilder +end # Plexus diff --git a/lib/plexus/ruby_compatibility.rb b/lib/plexus/ruby_compatibility.rb new file mode 100644 index 0000000..853f06d --- /dev/null +++ b/lib/plexus/ruby_compatibility.rb @@ -0,0 +1,17 @@ +if RUBY_VERSION < "1.9" + def ruby_18 + yield + end + + def ruby_19 + false + end +else + def ruby_18 + false + end + + def ruby_19 + yield + end +end diff --git a/lib/plexus/search.rb b/lib/plexus/search.rb new file mode 100644 index 0000000..bfcde4f --- /dev/null +++ b/lib/plexus/search.rb @@ -0,0 +1,510 @@ +module Plexus + # **Search/traversal algorithms.** + # + # This module defines a collection of search/traversal algorithms, in a unified API. + # Read through the doc to get familiar with the calling pattern. + # + # Options are mostly callbacks passed in as a hash. The following are valid, + # anything else is ignored: + # + # * `:enter_vertex` => `Proc` Called upon entry of a vertex. + # * `:exit_vertex` => `Proc` Called upon exit of a vertex. + # * `:root_vertex` => `Proc` Called when a vertex is the root of a tree. + # * `:start_vertex` => `Proc` Called for the first vertex of the search. + # * `:examine_edge` => `Proc` Called when an edge is examined. + # * `:tree_edge` => `Proc` Called when the edge is a member of the tree. + # * `:back_edge` => `Proc` Called when the edge is a back edge. + # * `:forward_edge` => `Proc` Called when the edge is a forward edge. + # * `:adjacent` => `Proc` which, given a vertex, returns adjacent nodes, defaults to adjacent call of graph useful for changing the definition of adjacent in some algorithms. + # * `:start` => vertex Specifies the vertex to start search from. + # + # If a `&block` instead of an option hash is specified, it defines `:enter_vertex`. + # + # Each search algorithm returns the list of vertexes as reached by `enter_vertex`. + # This allows for calls like, `g.bfs.each { |v| ... }` + # + # Can also be called like `bfs_examine_edge { |e| ... }` or + # `dfs_back_edge { |e| ... }` for any of the callbacks. + # + # A full example usage is as follows: + # + # ev = Proc.new { |x| puts "Enter vertex #{x}" } + # xv = Proc.new { |x| puts "Exit vertex #{x}" } + # sv = Proc.new { |x| puts "Start vertex #{x}" } + # ee = Proc.new { |x| puts "Examine Arc #{x}" } + # te = Proc.new { |x| puts "Tree Arc #{x}" } + # be = Proc.new { |x| puts "Back Arc #{x}" } + # fe = Proc.new { |x| puts "Forward Arc #{x}" } + # Digraph[1,2, 2,3, 3,4].dfs({ + # :enter_vertex => ev, + # :exit_vertex => xv, + # :start_vertex => sv, + # :examine_edge => ee, + # :tree_edge => te, + # :back_edge => be, + # :forward_edge => fe }) + # + # Which outputs: + # + # Start vertex 1 + # Enter vertex 1 + # Examine Arc (1=2) + # Tree Arc (1=2) + # Enter vertex 2 + # Examine Arc (2=3) + # Tree Arc (2=3) + # Enter vertex 3 + # Examine Arc (3=4) + # Tree Arc (3=4) + # Enter vertex 4 + # Examine Arc (1=4) + # Back Arc (1=4) + # Exit vertex 4 + # Exit vertex 3 + # Exit vertex 2 + # Exit vertex 1 + # => [1, 2, 3, 4] + module Search + + # Performs a breadth-first search. + # + # @param [Hash] options + def bfs(options = {}, &block) + plexus_search_helper(:shift, options, &block) + end + alias :bread_first_search :bfs + + # Performs a depth-first search. + # + # @param [Hash] options + def dfs(options = {}, &block) + plexus_search_helper(:pop, options, &block) + end + alias :depth_first_search :dfs + + # Routine which computes a spanning forest for the given search method. + # Returns two values: a hash of predecessors and an array of root nodes. + # + # @param [vertex] start + # @param [Symbol] routine the search method (`:dfs`, `:bfs`) + # @return [Array] predecessors and root nodes + def spanning_forest(start, routine) + predecessor = {} + roots = [] + te = Proc.new { |e| predecessor[e.target] = e.source } + rv = Proc.new { |v| roots << v } + send routine, :start => start, :tree_edge => te, :root_vertex => rv + [predecessor, roots] + end + + # Returns the dfs spanning forest for the given start node, see {Search#spanning_forest spanning_forest}. + # + # @param [vertex] start + # @return [Array] predecessors and root nodes + def dfs_spanning_forest(start) + spanning_forest(start, :dfs) + end + + # Returns the bfs spanning forest for the given start node, see {Search#spanning_forest spanning_forest}. + # + # @param [vertex] start + # @return [Array] predecessors and root nodes + def bfs_spanning_forest(start) + spanning_forest(start, :bfs) + end + + # Returns a hash of predecessors in a tree rooted at the start node. If this is a connected graph, + # then it will be a spanning tree containing all vertices. An easier way to tell if it's a + # spanning tree is to use a {Search#spanning_forest spanning_forest} call and check if there is a + # single root node. + # + # @param [vertex] start + # @param [Symbol] routine the search method (`:dfs`, `:bfs`) + # @return [Hash] predecessors vertices + def tree_from_vertex(start, routine) + predecessor = {} + correct_tree = false + te = Proc.new { |e| predecessor[e.target] = e.source if correct_tree } + rv = Proc.new { |v| correct_tree = (v == start) } + send routine, :start => start, :tree_edge => te, :root_vertex => rv + predecessor + end + + # Returns a hash of predecessors for the depth-first search tree rooted at the given node. + # + # @param [vertex] start + # @return [Hash] predecessors vertices + def dfs_tree_from_vertex(start) + tree_from_vertex(start, :dfs) + end + + # Returns a hash of predecessors for the breadth-first search tree rooted at the given node. + # + # @param [Proc] start + # @return [Hash] predecessors vertices + def bfs_tree_from_vertex(start) + tree_from_vertex(start, :bfs) + end + + # An inner class used for greater efficiency in {Search#lexicograph_bfs}. + # + # Original design taken from Golumbic's, *Algorithmic Graph Theory and Perfect Graphs* pg. 87-89. + class LexicographicQueue + # Called with the initial values. + # + # @param [Array] initial vertices values + def initialize(values) + @node = Struct.new(:back, :forward, :data) + @node.class_eval do + def hash + @hash + end + @@cnt = 0 + end + @set = {} + @tail = @node.new(nil, nil, Array.new(values)) + @tail.instance_eval { @hash = (@@cnt += 1) } + values.each { |a| @set[a] = @tail } + end + + # Pops an entry with the maximum lexical value from the queue. + # + # @return [vertex] + def pop + return nil unless @tail + value = @tail[:data].pop + @tail = @tail[:forward] while @tail and @tail[:data].size == 0 + @set.delete(value) + value + end + + # Increase the lexical value of the given values. + # + # @param [Array] vertices values + def add_lexeme(values) + fix = {} + + values.select { |v| @set[v] }.each do |w| + sw = @set[w] + if fix[sw] + s_prime = sw[:back] + else + s_prime = @node.new(sw[:back], sw, []) + s_prime.instance_eval { @hash = (@@cnt += 1) } + @tail = s_prime if @tail == sw + sw[:back][:forward] = s_prime if sw[:back] + sw[:back] = s_prime + fix[sw] = true + end + + s_prime[:data] << w + sw[:data].delete(w) + @set[w] = s_prime + end + + fix.keys.select { |n| n[:data].size == 0 }.each do |e| + e[:forward][:back] = e[:back] if e[:forward] + e[:back][:forward] = e[:forward] if e[:back] + end + end + + end + + # Lexicographic breadth-first search. + # + # The usual queue of vertices is replaced by a queue of *unordered subsets* + # of the vertices, which is sometimes refined but never reordered. + # + # Originally developed by Rose, Tarjan, and Leuker, *Algorithmic + # aspects of vertex elimination on graphs*, SIAM J. Comput. 5, 266-283 + # MR53 #12077 + # + # Implementation taken from Golumbic's, *Algorithmic Graph Theory and + # Perfect Graphs*, pg. 84-90. + # + # @return [vertex] + def lexicograph_bfs(&block) + lex_q = Plexus::Search::LexicographicQueue.new(vertices) + result = [] + num_vertices.times do + v = lex_q.pop + result.unshift(v) + lex_q.add_lexeme(adjacent(v)) + end + result.each { |r| block.call(r) } if block + result + end + + # A* Heuristic best first search. + # + # `start` is the starting vertex for the search. + # + # `func` is a `Proc` that when passed a vertex returns the heuristic + # weight of sending the path through that node. It must always + # be equal to or less than the true cost. + # + # `options` are mostly callbacks passed in as a hash, the default block is + # `:discover_vertex` and the weight is assumed to be the label for the {Arc}. + # The following options are valid, anything else is ignored: + # + # * `:weight` => can be a `Proc`, or anything else is accessed using the `[]` for the + # the label or it defaults to using + # the value stored in the label for the {Arc}. If it is a `Proc` it will + # pass the edge to the proc and use the resulting value. + # * `:discover_vertex` => `Proc` invoked when a vertex is first discovered + # and is added to the open list. + # * `:examine_vertex` => `Proc` invoked when a vertex is popped from the + # queue (i.e., it has the lowest cost on the open list). + # * `:examine_edge` => `Proc` invoked on each out-edge of a vertex + # immediately after it is examined. + # * `:edge_relaxed` => `Proc` invoked on edge `(u,v) if d[u] + w(u,v) < d[v]`. + # * `:edge_not_relaxed`=> `Proc` invoked if the edge is not relaxed (see above). + # * `:black_target` => `Proc` invoked when a vertex that is on the closed + # list is "rediscovered" via a more efficient path, and is re-added + # to the open list. + # * `:finish_vertex` => Proc invoked on a vertex when it is added to the + # closed list, which happens after all of its out edges have been + # examined. + # + # Can also be called like `astar_examine_edge {|e| ... }` or + # `astar_edge_relaxed {|e| ... }` for any of the callbacks. + # + # The criteria for expanding a vertex on the open list is that it has the + # lowest `f(v) = g(v) + h(v)` value of all vertices on open. + # + # The time complexity of A* depends on the heuristic. It is exponential + # in the worst case, but is polynomial when the heuristic function h + # meets the following condition: `|h(x) - h*(x)| < O(log h*(x))` where `h*` + # is the optimal heuristic, i.e. the exact cost to get from `x` to the `goal`. + # + # See also: [A* search algorithm](http://en.wikipedia.org/wiki/A*_search_algorithm) on Wikipedia. + # + # @param [vertex] start the starting vertex for the search + # @param [vertex] goal the vertex to reach + # @param [Proc] func heuristic weight computing process + # @param [Hash] options + # @return [Array(vertices), call, nil] an array of nodes in path, or calls block on all nodes, + # upon failure returns `nil` + def astar(start, goal, func, options, &block) + options.instance_eval "def handle_callback(sym,u) self[sym].call(u) if self[sym]; end" + + # Initialize. + d = { start => 0 } + color = { start => :gray } # Open is :gray, closed is :black. + parent = Hash.new { |k| parent[k] = k } + f = { start => func.call(start) } + queue = PriorityQueue.new.push(start, f[start]) + block.call(start) if block + + # Process queue. + until queue.empty? + u, dummy = queue.delete_min + options.handle_callback(:examine_vertex, u) + + # Unravel solution if the goal is reached. + if u == goal + solution = [goal] + while u != start + solution << parent[u] + u = parent[u] + end + return solution.reverse + end + + adjacent(u, :type => :edges).each do |e| + v = e.source == u ? e.target : e.source + options.handle_callback(:examine_edge, e) + w = cost(e, options[:weight]) + raise ArgumentError unless w + + if d[v].nil? or (w + d[u]) < d[v] + options.handle_callback(:edge_relaxed, e) + d[v] = w + d[u] + f[v] = d[v] + func.call(v) + parent[v] = u + + unless color[v] == :gray + options.handle_callback(:black_target, v) if color[v] == :black + color[v] = :gray + options.handle_callback(:discover_vertex, v) + queue.push v, f[v] + block.call(v) if block + end + else + options.handle_callback(:edge_not_relaxed, e) + end + end # adjacent(u) + + color[u] = :black + options.handle_callback(:finish_vertex, u) + end # queue.empty? + + nil # failure, on fall through + end # astar + + # `best_first` has all the same options as {Search#astar astar}, with `func` set to `h(v) = 0`. + # There is an additional option, `zero`, which should be defined as the zero element + # for the `+` operation performed on the objects used in the computation of cost. + # + # @param [vertex] start the starting vertex for the search + # @param [vertex] goal the vertex to reach + # @param [Proc] func heuristic weight computing process + # @param [Hash] options + # @param [Integer] zero (0) + # @return [Array(vertices), call, nil] an array of nodes in path, or calls block on all nodes, + # upon failure returns `nil` + def best_first(start, goal, options, zero = 0, &block) + func = Proc.new { |v| zero } + astar(start, goal, func, options, &block) + end + + # @private + alias_method :pre_search_method_missing, :method_missing + def method_missing(sym, *args, &block) + m1 = /^dfs_(\w+)$/.match(sym.to_s) + dfs((args[0] || {}).merge({ m1.captures[0].to_sym => block })) if m1 + m2 = /^bfs_(\w+)$/.match(sym.to_s) + bfs((args[0] || {}).merge({ m2.captures[0].to_sym => block })) if m2 + pre_search_method_missing(sym, *args, &block) unless m1 or m2 + end + + private + + # Performs the search using a specific algorithm and a set of options. + # + # @param [Symbol] op the algorithm to be used te perform the search + # @param [Hash] options + # @return [Object] result + def plexus_search_helper(op, options = {}, &block) + return nil if size == 0 + result = [] + + # Create the options hash handling callbacks. + options = {:enter_vertex => block, :start => to_a[0]}.merge(options) + options.instance_eval "def handle_vertex(sym,u) self[sym].call(u) if self[sym]; end" + options.instance_eval "def handle_edge(sym,e) self[sym].call(e) if self[sym]; end" + + # Create a waiting list, which is a queue or a stack, depending on the op specified. + # The first entry is the start vertex. + waiting = [options[:start]] + waiting.instance_eval "def next; #{op.to_s}; end" + + # Create a color map, all elements set to "unvisited" except for start vertex, + # which will be set to waiting. + color_map = vertices.inject({}) { |a,v| a[v] = :unvisited; a } + color_map.merge!(waiting[0] => :waiting) + options.handle_vertex(:start_vertex, waiting[0]) + options.handle_vertex(:root_vertex, waiting[0]) + + # Perform the actual search until nothing is "waiting". + until waiting.empty? + # Loop till the search iterator exhausts the waiting list. + visited_edges = {} # This prevents retraversing edges in undirected graphs. + until waiting.empty? + plexus_search_iteration(options, waiting, color_map, visited_edges, result, op == :pop) + end + # Waiting for the list to be exhausted, check if a new root vertex is available. + u = color_map.detect { |key,value| value == :unvisited } + waiting.push(u[0]) if u + options.handle_vertex(:root_vertex, u[0]) if u + end + + result + end + + # Performs a search iteration (step). + # + # @private + def plexus_search_iteration(options, waiting, color_map, visited_edges, result, recursive = false) + # Fetch the next waiting vertex in the list. + #sleep + u = waiting.next + options.handle_vertex(:enter_vertex, u) + result << u + + # Examine all adjacent outgoing edges, but only those not previously traversed. + adj_proc = options[:adjacent] || self.method(:adjacent).to_proc + adj_proc.call(u, :type => :edges, :direction => :out).reject { |w| visited_edges[w] }.each do |e| + e = e.reverse unless directed? or e.source == u # Preserves directionality where required. + v = e.target + options.handle_edge(:examine_edge, e) + visited_edges[e] = true + + case color_map[v] + # If it's unvisited, it goes into the waiting list. + when :unvisited + options.handle_edge(:tree_edge, e) + color_map[v] = :waiting + waiting.push(v) + # If it's recursive (i.e. dfs), then call self. + plexus_search_iteration(options, waiting, color_map, visited_edges, result, true) if recursive + when :waiting + options.handle_edge(:back_edge, e) + else + options.handle_edge(:forward_edge, e) + end + end + + # Done with this vertex! + options.handle_vertex(:exit_vertex, u) + color_map[u] = :visited + end + + public + + # Topological Sort Iterator. + # + # The topological sort algorithm creates a linear ordering of the vertices + # such that if edge (u,v) appears in the graph, then u comes before v in + # the ordering. The graph must be a directed acyclic graph (DAG). + # + # The iterator can also be applied to undirected graph or to a DG graph + # which contains a cycle. In this case, the Iterator does not reach all + # vertices. The implementation of acyclic? and cyclic? uses this fact. + # + # Can be called with a block as a standard ruby iterator, or can + # be used directly as it will return the result as an Array. + # + # @param [vertex] start (nil) the start vertex (nil will fallback on the first + # vertex inserted within the graph) + # @return [Array] a linear representation of the sorted graph + def topsort(start = nil, &block) + result = [] + go = true + back = Proc.new { |e| go = false } + push = Proc.new { |v| result.unshift(v) if go } + start ||= vertices[0] + dfs({ :exit_vertex => push, :back_edge => back, :start => start }) + result.each { |v| block.call(v) } if block + result + end + + # Does a top sort, but trudges forward if a cycle occurs. Use with caution. + # + # @param [vertex] start (nil) the start vertex (nil will fallback on the first + # vertex inserted within the graph) + # @return [Array] a linear representation of the sorted graph + def sort(start = nil, &block) + result = [] + push = Proc.new { |v| result.unshift(v) } + start ||= vertices[0] + dfs({ :exit_vertex => push, :start => start }) + result.each { |v| block.call(v) } if block + result + end + + # Returns true if a graph contains no cycles, false otherwise. + # + # @return [Boolean] + def acyclic? + topsort.size == size + end + + # Returns false if a graph contains no cycles, true otherwise. + # + # @return [Boolean] + def cyclic? + not acyclic? + end + end +end diff --git a/lib/graphy/strong_components.rb b/lib/plexus/strong_components.rb similarity index 95% rename from lib/graphy/strong_components.rb rename to lib/plexus/strong_components.rb index 14e8a99..24f1fa7 100644 --- a/lib/graphy/strong_components.rb +++ b/lib/plexus/strong_components.rb @@ -1,4 +1,4 @@ -module Graphy +module Plexus module StrongComponents # strong_components computes the strongly connected components # of a graph using Tarjan's algorithm based on DFS. See: Robert E. Tarjan @@ -15,7 +15,6 @@ module StrongComponents # from each other. # def strong_components - dfs_num = 0 stack = []; result = []; root = {}; comp = {}; number = {} @@ -49,7 +48,7 @@ def strong_components 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 @@ -69,7 +68,7 @@ def condensation # 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.graphy_inner_transitive_closure! + cgtc = condensation.plexus_inner_transitive_closure! cgtc.each do |cgv| cgtc.adjacent(cgv).each do |adj| cgv.each do |u| @@ -83,7 +82,7 @@ def transitive_closure! # is not changed. def transitive_closure() self.class.new(self).transitive_closure!; end - def graphy_inner_transitive_closure! # :nodoc: + def plexus_inner_transitive_closure! # :nodoc: sort.reverse.each do |u| adjacent(u).each do |v| adjacent(v).each {|w| add_edge!(u,w) unless edge?(u,w)} @@ -91,4 +90,4 @@ def graphy_inner_transitive_closure! # :nodoc: end; self end end # StrongComponents -end # Graphy +end # Plexus diff --git a/lib/plexus/support/support.rb b/lib/plexus/support/support.rb new file mode 100644 index 0000000..24005d6 --- /dev/null +++ b/lib/plexus/support/support.rb @@ -0,0 +1,9 @@ +module Plexus + # Errors + # TODO FIXME: must review all raise lines and streamline things + + # Base error class for the library. + class PlexusError < StandardError; end + + class NoArcError < PlexusError; end +end diff --git a/lib/plexus/undirected_graph.rb b/lib/plexus/undirected_graph.rb new file mode 100644 index 0000000..cfbbf02 --- /dev/null +++ b/lib/plexus/undirected_graph.rb @@ -0,0 +1,56 @@ +module Plexus + module UndirectedGraphBuilder + autoload :Algorithms, "plexus/undirected_graph/algorithms" + + include Plexus::GraphBuilder + extends_host + + module ClassMethods + def [](*a) + self.new.from_array(*a) + end + end + + def initialize(*params) + args = (params.pop if params.last.kind_of? Hash) || {} + args[:algorithmic_category] = Plexus::UndirectedGraphBuilder::Algorithms + super *(params << args) + end + end + + # This is a Digraph that allows for parallel edges, but does not allow loops. + module UndirectedPseudoGraphBuilder + include UndirectedGraphBuilder + extends_host + + module ClassMethods + def [](*a) + self.new.from_array(*a) + end + end + + def initialize(*params) + args = (params.pop if params.last.kind_of? Hash) || {} + args[:parallel_edges] = true + super *(params << args) + end + end + + # This is a Digraph that allows for parallel edges and loops. + module UndirectedMultiGraphBuilder + include UndirectedPseudoGraphBuilder + extends_host + + module ClassMethods + def [](*a) + self.new.from_array(*a) + end + end + + def initialize(*params) + args = (params.pop if params.last.kind_of? Hash) || {} + args[:loops] = true + super *(params << args) + end + end +end diff --git a/lib/graphy/undirected_graph/algorithms.rb b/lib/plexus/undirected_graph/algorithms.rb similarity index 89% rename from lib/graphy/undirected_graph/algorithms.rb rename to lib/plexus/undirected_graph/algorithms.rb index c687f48..6046acf 100644 --- a/lib/graphy/undirected_graph/algorithms.rb +++ b/lib/plexus/undirected_graph/algorithms.rb @@ -1,34 +1,32 @@ -module Graphy - - class UndirectedGraph - +module Plexus + module UndirectedGraphBuilder module Algorithms - + include Search include Biconnected include Comparability # UndirectedGraph is by definition undirected, always returns false def directed?() false; end - + # Redefine degree (default was sum) def degree(v) in_degree(v); end - + # A vertex of an undirected graph is balanced by definition def balanced?(v) true; end # UndirectedGraph uses Edge for the edge class. - def edge_class() @parallel_edges ? Graphy::MultiEdge : Graphy::Edge; end + def edge_class() @parallel_edges ? Plexus::MultiEdge : Plexus::Edge; end def remove_edge!(u, v=nil) - unless u.kind_of? Graphy::Arc - raise ArgumentError if @parallel_edges + unless u.kind_of? Plexus::Arc + raise ArgumentError if @parallel_edges u = edge_class[u,v] end super(u.reverse) unless u.source == u.target super(u) end - + # A triangulated graph is an undirected perfect graph that every cycle of length greater than # three possesses a chord. They have also been called chordal, rigid circuit, monotone transitive, # and perfect elimination graphs. @@ -48,29 +46,30 @@ def triangulated? end true end - + def chromatic_number return triangulated_chromatic_number if triangulated? - raise NotImplementedError + raise NotImplementedError end - + # An interval graph can have its vertices into one-to-one # correspondence with a set of intervals F of a linearly ordered # set (like the real line) such that two vertices are connected # by an edge of G if and only if their corresponding intervals # have nonempty intersection. def interval?() triangulated? and complement.comparability?; end - + # A permutation diagram consists of n points on each of two parallel # lines and n straight line segments matchin the points. The intersection # graph of the line segments is called a permutation graph. def permutation?() comparability? and complement.comparability?; end - + # An undirected graph is defined to be split if there is a partition - # V = S + K of its vertex set into a stable set S and a complete set K. + # V = S + K of its vertex set into a stable set S and a complete set K. def split?() triangulated? and complement.triangulated?; end - + private + # Implementation taken from Golumbic's, "Algorithmic Graph Theory and # Perfect Graphs" pg. 99 def triangulated_chromatic_number @@ -86,9 +85,7 @@ def triangulated_chromatic_number end end; chi end - - end # UndirectedGraphAlgorithms + end end - end diff --git a/lib/plexus/version.rb b/lib/plexus/version.rb new file mode 100644 index 0000000..909c382 --- /dev/null +++ b/lib/plexus/version.rb @@ -0,0 +1,6 @@ +module Plexus + MAJOR = 0 + MINOR = 5 + PATCH = 9 + VERSION = [MAJOR, MINOR, PATCH].join('.') +end diff --git a/plexus.gemspec b/plexus.gemspec new file mode 100644 index 0000000..4508f8f --- /dev/null +++ b/plexus.gemspec @@ -0,0 +1,28 @@ +# -*- encoding: utf-8 -*- +require './lib/plexus/version' + +Gem::Specification.new do |s| + s.name = %q{plexus} + s.version = Plexus::VERSION + s.authors = ["Bruce Williams", "Jean-Denis Vauguet "] + s.summary = "A framework for graph data structures and algorithms." + s.description = %q{This library is based on GRATR and RGL. + +Graph algorithms currently provided are: + +* Topological Sort +* Strongly Connected Components +* Transitive Closure +* Rural Chinese Postman +* Biconnected +} + s.email = %q{jd@vauguet.fr} + s.files = Dir["lib/**/*"] + Dir["vendor/**/*"] + Dir["spec/**/*"] + ["Gemfile", "LICENSE", "Rakefile", "README.md"] + s.homepage = %q{http://github.com/chikamichi/plexus} + s.add_dependency "rake" + s.add_dependency "activesupport" + s.add_dependency "facets" + s.add_development_dependency "rspec" + s.add_development_dependency "yard" +end + diff --git a/spec/edge_spec.rb b/spec/edge_spec.rb index e4b030f..74aabde 100644 --- a/spec/edge_spec.rb +++ b/spec/edge_spec.rb @@ -1,13 +1,12 @@ -require File.join(File.dirname(__FILE__), 'spec_helper') - -describe "Arc" do # :nodoc: +require 'spec_helper' +describe Arc do before do @e = Arc.new(1,2,'boo') @u = Edge.new(1,2,'hoo') end - describe "edge_new" do + describe "#edge_new" do it do proc { Arc.new }.should raise_error(ArgumentError) proc { Arc.new(1) }.should raise_error(ArgumentError) @@ -16,7 +15,7 @@ end end - describe "edge_getters" do + describe "#edge_getters" do it do @e.source.should == 1 @e.target.should == 2 @@ -25,14 +24,14 @@ @e[0].should == 1 @e[1].should == 2 @e[2].should == 'boo' - + @e[-3].should == 1 @e[-2].should == 2 @e[-1].should == 'boo' - proc { @e[-4] }.should raise_error(IndexError) - proc { @e[3] }.should raise_error(IndexError) - + proc { @e[-4] }.should raise_error(IndexError) + proc { @e[3] }.should raise_error(IndexError) + @e['source'].should == 1 @e['target'].should == 2 @e['label'].should == 'boo' @@ -43,7 +42,7 @@ end end - describe "edge_setters" do + describe "#edge_setters" do it do @e.source = 23 @e.target = 42 @@ -82,7 +81,7 @@ end end - describe "edge_simple_methods" do + describe "#edge_simple_methods" do it do @e.to_a.should == [1,2,'boo'] @e.to_s.should == "(1-2 'boo')" @@ -99,14 +98,14 @@ end end - describe "edge_sort" do + describe "#edge_sort" do it do x = [ Arc.new(2,3), Arc.new(1,3), Arc.new(1,2), Arc.new(2,1) ].sort x.should == [Arc.new(1,2), Arc.new(1,3), Arc.new(2,1), Arc.new(2,3)] end end - describe "undirected_edge_new" do + describe "#undirected_edge_new" do it do proc { Edge.new }.should raise_error(ArgumentError) proc { Edge.new(1) }.should raise_error(ArgumentError) @@ -115,7 +114,7 @@ end end - describe "undirected_edge_getters" do + describe "#undirected_edge_getters" do it do @u.source.should == 1 @u.target.should == 2 @@ -124,7 +123,7 @@ end end - describe "undirected_edge_methods" do + describe "#undirected_edge_methods" do it do @u.label = nil @u.to_s.should == "(1=2)" @@ -138,14 +137,14 @@ end end - describe "undirected_edge_sort" do + describe "#undirected_edge_sort" do it do x = [Edge.new(12, 1), Edge.new(2,11)].sort x.should == [Edge.new(2,11), Edge.new(1,12)] end end - - describe "hash" do + + describe "#hash" do it do Arc[1,2,:c].should == Arc[1,2,:b] Arc[1,2,:c].hash.should == Arc[1,2,:b].hash diff --git a/spec/inspection_spec.rb b/spec/inspection_spec.rb index 2a36ee4..642b176 100644 --- a/spec/inspection_spec.rb +++ b/spec/inspection_spec.rb @@ -1,21 +1,21 @@ -require File.join(File.dirname(__FILE__), 'spec_helper') +require 'spec_helper' describe "Inspection" do # :nodoc: - before do - @dg = DirectedMultiGraph[ - [0,0,1] => 1, - [1,2,2] => 2, - [1,3,3] => 4, - [1,4,4] => nil, - [4,1,5] => 8, - [1,2,6] => 16, - [3,3,7] => 32, - [3,3,8] => 64 ] + @dg = DirectedMultiGraph[ + [0,0,1] => 1, + [1,2,2] => 2, + [1,3,3] => 4, + [1,4,4] => nil, + [4,1,5] => 8, + [1,2,6] => 16, + [3,3,7] => 32, + [3,3,8] => 64 + ] @dg[3] = 128 @dg[0] = 256 end - + describe "inspection_without_labels" do it do @dg = Digraph[1,2,3,4,5,6] @@ -23,7 +23,7 @@ reflect.should == @dg end end - + describe "inspection_with_labels" do it do inspect = @dg.inspect @@ -34,7 +34,5 @@ reflect.edges.inject(0) { |a,e| a += (reflect[e] || 0)}.should == 127 reflect.vertices.inject(0) {|a,v| a += (reflect[v] || 0)}.should == 384 end - end - end diff --git a/spec/neighborhood_spec.rb b/spec/neighborhood_spec.rb index 61f61df..56f767d 100644 --- a/spec/neighborhood_spec.rb +++ b/spec/neighborhood_spec.rb @@ -1,7 +1,6 @@ require File.join(File.dirname(__FILE__), 'spec_helper') -describe "Neighborhood" do # :nodoc: - +describe "Neighborhood" do before do @d = Digraph[:a,:b, :a,:f, :b,:g, @@ -12,7 +11,7 @@ :g,:a, :g,:e] @w = [:a,:b] end - + describe "open_out_neighborhood" do it do @d.set_neighborhood([:a], :in).should == [:g] @@ -24,7 +23,7 @@ ([:c] - @d.open_pth_neighborhood(@w, 4, :out)).should == [] end end - + describe "closed_out_neighborhood" do it do (@w - @d.closed_pth_neighborhood(@w, 0, :out)).should == [] @@ -34,5 +33,4 @@ ([:a,:b,:c,:d,:e,:f,:g] - @d.closed_pth_neighborhood(@w, 4, :out)).should == [] end end - end diff --git a/spec/properties_spec.rb b/spec/properties_spec.rb index 70fda57..0294e3a 100644 --- a/spec/properties_spec.rb +++ b/spec/properties_spec.rb @@ -1,6 +1,6 @@ require File.join(File.dirname(__FILE__), 'spec_helper') -require 'graphy/dot' +require 'plexus/dot' # This test runs the classes from Appendix F in # _Algorithmic_Graph_Theory_and_Perfect_Graphs, diff --git a/spec/search_spec.rb b/spec/search_spec.rb index 30a83f3..10d0d78 100644 --- a/spec/search_spec.rb +++ b/spec/search_spec.rb @@ -9,14 +9,14 @@ end # "Algorithmic Graph Theory and Perfect Graphs", Martin Charles - # Golumbic, 1980, Academic Press, page 39, Propery (D1) and (D2) of + # Golumbic, 1980, Academic Press, page 39, Propery (D1) and (D2) of # depth first search describe "dfs_properties" do it do dfs = {} father = {} @directed.each do |vertex| - assign_dfsnumber_ancestry(@directed, dfs, father, vertex) + assign_dfsnumber_ancestry(@directed, dfs, father, vertex) # Property (D1) father.keys.each {|v| dfs[father[v]].should be < dfs[v] } # Property (D2) @@ -30,15 +30,15 @@ end # "Algorithmic Graph Theory and Perfect Graphs", Martin Charles - # Golumbic, 1980, Academic Press, page 40, Propery (B1), (B2) and (B3) of + # Golumbic, 1980, Academic Press, page 40, Propery (B1), (B2) and (B3) of # breadth first search describe "bfs_properties" do it do - level = {} + level = {} father = {} bfs = {} @directed.each do |vertex| - assign_bfsnumber_ancestry(@directed, bfs, level, father, vertex) + assign_bfsnumber_ancestry(@directed, bfs, level, father, vertex) # Property (B1) father.keys.each do |v| bfs[father[v]].should be < bfs[v] @@ -107,7 +107,7 @@ # Heuristic from "Artificial Intelligence: A Modern Approach" by Stuart # Russell ande Peter Norvig, Prentice-Hall 2nd Edition, pg 95 - straight_line_to_Bucharest = + straight_line_to_Bucharest = { 'Arad' => 366, 'Bucharest' => 0, @@ -142,57 +142,57 @@ fv = Proc.new {|v| list << "fv #{v}" } er = Proc.new {|e| list << "er #{e}" } enr = Proc.new {|e| list << "enr #{e}" } - + options = { :discover_vertex => dv, :examine_vertex => ev, :black_target => bt, :finish_vertex => fv, :edge_relaxed => er, :edge_not_relaxed => enr } - + result = romania.astar('Arad', 'Bucharest', h, options) result.should == ["Arad", "Sibiu", "Rimnicu Vilcea", "Pitesti", "Bucharest"] # This isn't the greatest test since the exact ordering is not # not specified by the algorithm. If someone has a better idea, please fix list.should == ["ev Arad", - "er (Arad=Sibiu '99')", - "dv Sibiu", - "er (Arad=Timisoara '138')", - "dv Timisoara", - "er (Arad=Zerind '75')", - "dv Zerind", - "fv Arad", - "ev Sibiu", - "er (Rimnicu Vilcea=Sibiu '80')", - "dv Rimnicu Vilcea", - "er (Fagaras=Sibiu '99')", - "dv Fagaras", - "er (Oradea=Sibiu '151')", - "dv Oradea", - "enr (Arad=Sibiu '99')", - "fv Sibiu", - "ev Rimnicu Vilcea", - "enr (Rimnicu Vilcea=Sibiu '80')", - "er (Craiova=Rimnicu Vilcea '146')", - "dv Craiova", - "er (Pitesti=Rimnicu Vilcea '97')", - "dv Pitesti", - "fv Rimnicu Vilcea", - "ev Fagaras", - "enr (Fagaras=Sibiu '99')", - "er (Bucharest=Fagaras '211')", - "dv Bucharest", - "fv Fagaras", - "ev Pitesti", - "enr (Pitesti=Rimnicu Vilcea '97')", - "er (Bucharest=Pitesti '101')", - "enr (Craiova=Pitesti '138')", - "fv Pitesti", - "ev Bucharest"] + "er (Arad=Sibiu '99')", + "dv Sibiu", + "er (Arad=Timisoara '138')", + "dv Timisoara", + "er (Arad=Zerind '75')", + "dv Zerind", + "fv Arad", + "ev Sibiu", + "er (Rimnicu Vilcea=Sibiu '80')", + "dv Rimnicu Vilcea", + "er (Fagaras=Sibiu '99')", + "dv Fagaras", + "er (Oradea=Sibiu '151')", + "dv Oradea", + "enr (Arad=Sibiu '99')", + "fv Sibiu", + "ev Rimnicu Vilcea", + "enr (Rimnicu Vilcea=Sibiu '80')", + "er (Craiova=Rimnicu Vilcea '146')", + "dv Craiova", + "er (Pitesti=Rimnicu Vilcea '97')", + "dv Pitesti", + "fv Rimnicu Vilcea", + "ev Fagaras", + "enr (Fagaras=Sibiu '99')", + "er (Bucharest=Fagaras '211')", + "dv Bucharest", + "fv Fagaras", + "ev Pitesti", + "enr (Pitesti=Rimnicu Vilcea '97')", + "er (Bucharest=Pitesti '101')", + "enr (Craiova=Pitesti '138')", + "fv Pitesti", + "ev Bucharest"] end end - + describe "bfs_spanning_forest" do it do predecessor, roots = @tree.bfs_spanning_forest(1) @@ -203,7 +203,7 @@ roots.sort.should == [1,3,5,6,23] end end - + describe "dfs_spanning_forest" do it do predecessor, roots = @tree.dfs_spanning_forest(1) @@ -214,7 +214,7 @@ roots.sort.should == [1,3,5,6,23] end end - + describe "tree_from_vertex" do it do @tree.bfs_tree_from_vertex(1).should == {5=>2, 6=>2, 7=>6, 2=>1, 3=>1, 4=>1} diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 698c6fb..5e62dae 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,14 +1,10 @@ -$LOAD_PATH.unshift(File.dirname(__FILE__)) -$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) -require 'graphy' -require 'spec' -require 'spec/autorun' +require File.expand_path("../../lib/plexus.rb", __FILE__) -include Graphy +require 'plexus' +include Plexus module AncestryHelper - - # "Algorithmic Graph Theory and Perfect Graphs", Martin Charles + # "Algorithmic Graph Theory and Perfect Graphs", Martin Charles # Golumbic, 1980, Academic Press, page 38, Figure 2.6 def assign_dfsnumber_ancestry(graph, dfsnumber, father, start) i = 0 @@ -19,8 +15,8 @@ def assign_dfsnumber_ancestry(graph, dfsnumber, father, start) graph.dfs({:enter_vertex => ev, :tree_edge => te, :start => start}) end - # "Algorithmic Graph Theory and Perfect Graphs", Martin Charles - # Golumbic, 1980, Academic Press, page 40, Figure 2.7 + # "Algorithmic Graph Theory and Perfect Graphs", Martin Charles + # Golumbic, 1980, Academic Press, page 40, Figure 2.7 def assign_bfsnumber_ancestry(graph, bfsnum, level, father, start) i = 0 bfsnum.clear @@ -29,12 +25,12 @@ def assign_bfsnumber_ancestry(graph, bfsnum, level, father, start) rt = Proc.new {|v| level[v] = 0 } ev = Proc.new {|v| bfsnum[v]=(i+=1);level[v]=(level[father[v]]+1) if father[v]} te = Proc.new {|e| father[e.target] = e.source } - graph.dfs({:enter_vertex => ev, :tree_edge => te, + graph.dfs({:enter_vertex => ev, :tree_edge => te, :root_vertex => rt, :start => start}) end - # Is v an ancestor of u + # Is v an ancestor of u? def ancestor?(father, u, v) i = 1 while v @@ -51,6 +47,13 @@ def related?(father,u,v) end -Spec::Runner.configure do |config| +RSpec.configure do |config| config.include AncestryHelper + # Remove this line if you don't want RSpec's should and should_not + # methods or matchers + require 'rspec/expectations' + config.include RSpec::Matchers + + # == Mock Framework + config.mock_with :rspec end diff --git a/spec/triangulated_spec.rb b/spec/triangulated_spec.rb index b8834f1..3c2c281 100644 --- a/spec/triangulated_spec.rb +++ b/spec/triangulated_spec.rb @@ -100,7 +100,7 @@ describe "lexicographic_queue" do it do - q = Graphy::Search::LexicographicQueue.new([1,2,3,4,5,6,7,8,9]) + q = Plexus::Search::LexicographicQueue.new([1,2,3,4,5,6,7,8,9]) q.pop.should == 9 q.add_lexeme([3,4,5,6,7,8]) q.pop.should == 8