plexus 0.5.4 → 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/Gemfile +3 -0
  2. data/LICENSE +37 -0
  3. data/README.md +208 -0
  4. data/Rakefile +25 -0
  5. data/lib/plexus.rb +90 -0
  6. data/lib/plexus/adjacency_graph.rb +225 -0
  7. data/lib/plexus/arc.rb +60 -0
  8. data/lib/plexus/arc_number.rb +50 -0
  9. data/lib/plexus/biconnected.rb +84 -0
  10. data/lib/plexus/chinese_postman.rb +91 -0
  11. data/lib/plexus/classes/graph_classes.rb +28 -0
  12. data/lib/plexus/common.rb +63 -0
  13. data/lib/plexus/comparability.rb +63 -0
  14. data/lib/plexus/directed_graph.rb +78 -0
  15. data/lib/plexus/directed_graph/algorithms.rb +95 -0
  16. data/lib/plexus/directed_graph/distance.rb +167 -0
  17. data/lib/plexus/dot.rb +94 -0
  18. data/lib/plexus/edge.rb +38 -0
  19. data/lib/plexus/ext.rb +79 -0
  20. data/lib/plexus/graph.rb +628 -0
  21. data/lib/plexus/graph_api.rb +35 -0
  22. data/lib/plexus/labels.rb +112 -0
  23. data/lib/plexus/maximum_flow.rb +77 -0
  24. data/lib/plexus/ruby_compatibility.rb +17 -0
  25. data/lib/plexus/search.rb +510 -0
  26. data/lib/plexus/strong_components.rb +93 -0
  27. data/lib/plexus/support/support.rb +9 -0
  28. data/lib/plexus/undirected_graph.rb +56 -0
  29. data/lib/plexus/undirected_graph/algorithms.rb +90 -0
  30. data/lib/plexus/version.rb +6 -0
  31. data/spec/biconnected_spec.rb +27 -0
  32. data/spec/chinese_postman_spec.rb +27 -0
  33. data/spec/community_spec.rb +44 -0
  34. data/spec/complement_spec.rb +27 -0
  35. data/spec/digraph_distance_spec.rb +121 -0
  36. data/spec/digraph_spec.rb +339 -0
  37. data/spec/dot_spec.rb +48 -0
  38. data/spec/edge_spec.rb +158 -0
  39. data/spec/inspection_spec.rb +38 -0
  40. data/spec/multi_edge_spec.rb +32 -0
  41. data/spec/neighborhood_spec.rb +36 -0
  42. data/spec/properties_spec.rb +146 -0
  43. data/spec/search_spec.rb +227 -0
  44. data/spec/spec.opts +4 -0
  45. data/spec/spec_helper.rb +59 -0
  46. data/spec/strong_components_spec.rb +61 -0
  47. data/spec/triangulated_spec.rb +125 -0
  48. data/spec/undirected_graph_spec.rb +220 -0
  49. data/vendor/priority-queue/CHANGELOG +33 -0
  50. data/vendor/priority-queue/Makefile +140 -0
  51. data/vendor/priority-queue/README +133 -0
  52. data/vendor/priority-queue/benchmark/dijkstra.rb +171 -0
  53. data/vendor/priority-queue/compare_comments.rb +49 -0
  54. data/vendor/priority-queue/doc/c-vs-rb.png +0 -0
  55. data/vendor/priority-queue/doc/compare_big.gp +14 -0
  56. data/vendor/priority-queue/doc/compare_big.png +0 -0
  57. data/vendor/priority-queue/doc/compare_small.gp +15 -0
  58. data/vendor/priority-queue/doc/compare_small.png +0 -0
  59. data/vendor/priority-queue/doc/results.csv +37 -0
  60. data/vendor/priority-queue/ext/priority_queue/CPriorityQueue/extconf.rb +2 -0
  61. data/vendor/priority-queue/ext/priority_queue/CPriorityQueue/priority_queue.c +947 -0
  62. data/vendor/priority-queue/lib/priority_queue.rb +14 -0
  63. data/vendor/priority-queue/lib/priority_queue/c_priority_queue.rb +1 -0
  64. data/vendor/priority-queue/lib/priority_queue/poor_priority_queue.rb +46 -0
  65. data/vendor/priority-queue/lib/priority_queue/ruby_priority_queue.rb +526 -0
  66. data/vendor/priority-queue/priority_queue.so +0 -0
  67. data/vendor/priority-queue/setup.rb +1551 -0
  68. data/vendor/priority-queue/test/priority_queue_test.rb +371 -0
  69. data/vendor/rdot.rb +360 -0
  70. metadata +100 -10
@@ -0,0 +1,33 @@
1
+ 0.1.2
2
+ * Repaired benchmark
3
+ * Added c_priority_queue wrapper, such that one can now require versions
4
+ explicitly using:
5
+ require "priority_queue/(c|ruby|poor)_priority_queue"
6
+ * Improved README
7
+
8
+ 0.1.1
9
+ * Removed debug cruft
10
+ * Added more documentation and examples
11
+ * Readme typo fixed
12
+ * I had the tests commented out
13
+ * Removed a bug when pushing twice onto the c priority queue (and added a test)
14
+
15
+ 0.1.0
16
+ * API changes
17
+ * Added lots of unit tests
18
+ * Restructured
19
+ * Fallback to ruby version if c version is not available
20
+ * Added Efficent Pure Ruby Implementation (3 times slower than c version in
21
+ dijkstras algorithm)
22
+ * Added "Poor Mans Priority Queue" as a simple reference Implementation
23
+ * Minor improvements
24
+ * Added possibility to increase keys
25
+ * Minor bugs fixed
26
+ * Released as a .tar.gz (setup.rb)
27
+ * Released as a .gem (Anybody want to improve the distribution or point me to
28
+ some information on how to relase as .tar.gz and as .gem without too much
29
+ ado)
30
+
31
+ 0.0.0:
32
+ * First c-implementation
33
+ * Experimental Release
@@ -0,0 +1,140 @@
1
+
2
+ SHELL = /bin/sh
3
+
4
+ #### Start of system configuration section. ####
5
+
6
+ srcdir = .
7
+ topdir = /usr/lib/ruby/1.8/i486-linux
8
+ hdrdir = $(topdir)
9
+ VPATH = $(srcdir):$(topdir):$(hdrdir)
10
+ prefix = $(DESTDIR)/usr
11
+ exec_prefix = $(prefix)
12
+ sitedir = $(DESTDIR)/usr/local/lib/site_ruby
13
+ rubylibdir = $(libdir)/ruby/$(ruby_version)
14
+ archdir = $(rubylibdir)/$(arch)
15
+ sbindir = $(exec_prefix)/sbin
16
+ datadir = $(prefix)/share
17
+ includedir = $(prefix)/include
18
+ infodir = $(prefix)/info
19
+ sysconfdir = $(DESTDIR)/etc
20
+ mandir = $(datadir)/man
21
+ libdir = $(exec_prefix)/lib
22
+ sharedstatedir = $(prefix)/com
23
+ oldincludedir = $(DESTDIR)/usr/include
24
+ sitearchdir = $(sitelibdir)/$(sitearch)
25
+ bindir = $(exec_prefix)/bin
26
+ localstatedir = $(DESTDIR)/var
27
+ sitelibdir = $(sitedir)/$(ruby_version)
28
+ libexecdir = $(exec_prefix)/libexec
29
+
30
+ CC = gcc
31
+ LIBRUBY = $(LIBRUBY_SO)
32
+ LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a
33
+ LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME)
34
+ LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static
35
+
36
+ CFLAGS = -fPIC -Wall -g -O2 -fPIC
37
+ CPPFLAGS = -I. -I$(topdir) -I$(hdrdir) -I$(srcdir)
38
+ CXXFLAGS = $(CFLAGS)
39
+ DLDFLAGS =
40
+ LDSHARED = $(CC) -shared
41
+ AR = ar
42
+ EXEEXT =
43
+
44
+ RUBY_INSTALL_NAME = ruby1.8
45
+ RUBY_SO_NAME = ruby1.8
46
+ arch = i486-linux
47
+ sitearch = i486-linux
48
+ ruby_version = 1.8
49
+ ruby = /usr/bin/ruby1.8
50
+ RUBY = $(ruby)
51
+ RM = rm -f
52
+ MAKEDIRS = mkdir -p
53
+ INSTALL = /usr/bin/install -c
54
+ INSTALL_PROG = $(INSTALL) -m 0755
55
+ INSTALL_DATA = $(INSTALL) -m 644
56
+ COPY = cp
57
+
58
+ #### End of system configuration section. ####
59
+
60
+ preload =
61
+
62
+ libpath = $(libdir)
63
+ LIBPATH = -L"$(libdir)"
64
+ DEFFILE =
65
+
66
+ CLEANFILES =
67
+ DISTCLEANFILES =
68
+
69
+ extout =
70
+ extout_prefix =
71
+ target_prefix =
72
+ LOCAL_LIBS =
73
+ LIBS = $(LIBRUBYARG_SHARED) -lpthread -ldl -lcrypt -lm -lc
74
+ SRCS = priority_queue.c
75
+ OBJS = priority_queue.o
76
+ TARGET = priority_queue
77
+ DLLIB = $(TARGET).so
78
+ STATIC_LIB =
79
+
80
+ RUBYCOMMONDIR = $(sitedir)$(target_prefix)
81
+ RUBYLIBDIR = $(sitelibdir)$(target_prefix)
82
+ RUBYARCHDIR = $(sitearchdir)$(target_prefix)
83
+
84
+ TARGET_SO = $(DLLIB)
85
+ CLEANLIBS = $(TARGET).so $(TARGET).il? $(TARGET).tds $(TARGET).map
86
+ CLEANOBJS = *.o *.a *.s[ol] *.pdb *.exp *.bak
87
+
88
+ all: $(DLLIB)
89
+ static: $(STATIC_LIB)
90
+
91
+ clean:
92
+ @-$(RM) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES)
93
+
94
+ distclean: clean
95
+ @-$(RM) Makefile extconf.h conftest.* mkmf.log
96
+ @-$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES)
97
+
98
+ realclean: distclean
99
+ install: install-so install-rb
100
+
101
+ install-so: $(RUBYARCHDIR)
102
+ install-so: $(RUBYARCHDIR)/$(DLLIB)
103
+ $(RUBYARCHDIR)/$(DLLIB): $(DLLIB)
104
+ $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR)
105
+ install-rb: pre-install-rb install-rb-default
106
+ install-rb-default: pre-install-rb-default
107
+ pre-install-rb pre-install-rb-default: $(RUBYLIBDIR)
108
+ $(RUBYARCHDIR):
109
+ $(MAKEDIRS) $@
110
+ $(RUBYLIBDIR):
111
+ $(MAKEDIRS) $@
112
+
113
+ site-install: site-install-so site-install-rb
114
+ site-install-so: install-so
115
+ site-install-rb: install-rb
116
+
117
+ .SUFFIXES: .c .m .cc .cxx .cpp .C .o
118
+
119
+ .cc.o:
120
+ $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $<
121
+
122
+ .cxx.o:
123
+ $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $<
124
+
125
+ .cpp.o:
126
+ $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $<
127
+
128
+ .C.o:
129
+ $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c $<
130
+
131
+ .c.o:
132
+ $(CC) $(CFLAGS) $(CPPFLAGS) -c $<
133
+
134
+ $(DLLIB): $(OBJS)
135
+ @-$(RM) $@
136
+ $(LDSHARED) $(DLDFLAGS) $(LIBPATH) -o $@ $(OBJS) $(LOCAL_LIBS) $(LIBS)
137
+
138
+
139
+
140
+ $(OBJS): ruby.h defines.h
@@ -0,0 +1,133 @@
1
+ # Ruby extension implementing a priority queue
2
+
3
+ ## Description
4
+ This is a fibonacci-heap priority-queue implementation. That means
5
+
6
+ insert: O(1)
7
+ decrease_priority: Amortized O(1)
8
+ delete_min: Amortized O(log n)
9
+
10
+ This project is different from K. Kodamas PQueue in that it allows a decrease
11
+ key operation. That makes PriorityQueue usable for algorithms like dijkstras
12
+ shortest path algorithm, while PQueue is more suitable for Heapsort and the
13
+ like.
14
+
15
+ ## Legal stuff
16
+ (c) 2005 Brian Schr�der
17
+
18
+ Please submit bugreports to priority_queue@brian-schroeder.de
19
+
20
+ This extension is under the same license as ruby.
21
+
22
+ Do not hold me reliable for anything that happens to you, your programs or
23
+ anything else because of this extension. It worked for me, but there is no
24
+ guarantee it will work for you.
25
+
26
+ ## Requirements
27
+ * Ruby 1.8
28
+ * c Compiler
29
+
30
+ ## Installation
31
+
32
+ ### Installing from source
33
+
34
+ De-compress archive and enter its top directory.
35
+ Then type:
36
+
37
+ ($ su)
38
+ # ruby setup.rb
39
+
40
+ These simple step installs this program under the default
41
+ location of Ruby libraries. You can also install files into
42
+ your favorite directory by supplying setup.rb some options.
43
+ Try "ruby setup.rb --help".
44
+
45
+ ### Installing a ruby gem
46
+
47
+ ($ su)
48
+ # gem install PriorityQueue
49
+
50
+ ## Usage
51
+
52
+ In this priority queue implementation the queue behaves similarly to a hash
53
+ that maps objects onto priorities.
54
+
55
+ ### Hash Interface
56
+ require 'priority_queue'
57
+
58
+ q = PriorityQueue.new
59
+ q["node1"] = 0
60
+ q["node2"] = 1
61
+ q.min #=> "node1"
62
+ q[q.min] #=> 0
63
+ q.min_value #=> 0
64
+
65
+ q["node2"] = -1
66
+ q.delete_min #=> "node2", 1
67
+ q["node2"] #= nil
68
+ q["node3"] = 1
69
+
70
+ q.delete("node3") #=> "node3", 1
71
+ q.delete("node2") #=> nil
72
+
73
+
74
+ ### Queue Interface
75
+ require 'priority_queue'
76
+
77
+ q = PriorityQueue.new
78
+ q.push "node1", 0
79
+ q.push "node2", 1
80
+
81
+ q.min #=> "node1"
82
+
83
+ q.decrease_priority("node2", -1)
84
+
85
+ q.pop_min #=> "node2"
86
+ q.min #=> "node1"
87
+
88
+ for more exmples look into the documentation, the unit tests and the benchmark
89
+ suite.
90
+
91
+ ### Dijkstras shortest path algorithm
92
+ def dijkstra(start_node)
93
+ # Nodes that may have neighbours wich can be relaxed further
94
+ active = PriorityQueue.new
95
+ # Best distances found so far
96
+ distances = Hash.new { 1.0 / 0.0 }
97
+ # Parent pointers describing shortest paths for all nodes
98
+ parents = Hash.new
99
+
100
+ # Initialize with start node
101
+ active[start_node] = 0
102
+ until active.empty?
103
+ u, distance = active.delete_min
104
+ distances[u] = distance
105
+ d = distance + 1
106
+ u.neighbours.each do | v |
107
+ next unless d < distances[v] # we can't relax this one
108
+ active[v] = distances[v] = d
109
+ parents[v] = u
110
+ end
111
+ end
112
+ parents
113
+ end
114
+
115
+ ## Performance
116
+ The benchmark directory contains an example where a random graph is created and
117
+ the shortests paths from a random node in this graph to all other nodes are
118
+ calculated with dijkstras shortests path algorithm. The algorithm is used to
119
+ compare the three different priority queue implementations in this package.
120
+
121
+ * PoorPriorityQueue: A minimal priority queue implementation wich has
122
+ delete_min in O(n).
123
+ * RubyPriorityQueue: An efficent implementation in pure ruby.
124
+ * CPriorityQueue: The same efficent implementation as a c extension.
125
+
126
+ The results are shown here
127
+
128
+ ![Runtime for graphs of up to 8_000 Nodes](doc/compare_small.png "Runtime for graphs of up to 8_000 Nodes")
129
+
130
+ ![Runtime for graphs of up to 600_000 Nodes](doc/compare_big.png "Runtime for graphs of up to 600_000 Nodes")
131
+
132
+ ## Todo
133
+ * Only write documentation once
@@ -0,0 +1,171 @@
1
+ $:.unshift "~/lib/ruby"
2
+ require 'priority_queue/ruby_priority_queue'
3
+ require 'priority_queue/poor_priority_queue'
4
+ require 'priority_queue/c_priority_queue'
5
+ require 'benchmark'
6
+
7
+ class Node
8
+ attr_reader :neighbours, :id
9
+
10
+ def initialize(id)
11
+ @neighbours = []
12
+ @id = id
13
+ end
14
+
15
+ def inspect
16
+ to_s
17
+ end
18
+
19
+ def to_s
20
+ "(#{@id})"
21
+ end
22
+ end
23
+
24
+ # Build a graph by adding nodes with random connections
25
+
26
+ # Return a random graph with an average degree of degree
27
+ def make_graph(nodes, degree)
28
+ nodes = Array.new(nodes) { | i | Node.new(i.to_s) }
29
+ nodes.each do | n |
30
+ (degree / 2).times do
31
+ true while (n1 = nodes[rand(nodes.length)]) == n
32
+ n.neighbours << nodes[rand(nodes.length)]
33
+ n1.neighbours << n
34
+ n.neighbours << n1
35
+ end
36
+ end
37
+ end
38
+
39
+ def draw_graph(nodes, out)
40
+ dot = [] << "graph g {"
41
+ nodes.each do | n1 |
42
+ dot << "N#{n1.id} [label='#{n1.id}'];"
43
+ n1.neighbours.each do | n2 |
44
+ dot << "N#{n1.id} -- N#{n2.id};" if n1.id <= n2.id
45
+ end
46
+ end
47
+ dot << "}"
48
+
49
+ # system "echo '#{dot}' | neato -Gepsilon=0.001 -Goverlap=scale -Gsplines=true -Gsep=.4 -Tps -o #{out}"
50
+ system "echo '#{dot}' | neato -Gepsilon=0.05 -Goverlap=scale -Gsep=.4 -Tps -o #{out}"
51
+ end
52
+
53
+ def dijkstra(start_node, queue_klass)
54
+ # Priority Queue with unfinished nodes
55
+ active = queue_klass.new
56
+ # Distances for all nodes
57
+ distances = Hash.new { 1.0 / 0.0 }
58
+ # Parent pointers describing shortest paths for all nodes
59
+ parents = Hash.new
60
+
61
+ # Initialize with start node
62
+ active[start_node] = 0
63
+ until active.empty?
64
+ u, distance = active.delete_min
65
+ distances[u] = distance
66
+ d = distance + 1
67
+ u.neighbours.each do | v |
68
+ next unless d < distances[v] # we can't relax this one
69
+ active[v] = distances[v] = d
70
+ parents[v] = u
71
+ end
72
+ end
73
+ end
74
+
75
+ srand
76
+
77
+ sizes = Array.new(4) { | base | Array.new(9) { | mult | (mult+1) * 10**(base+2) } }.flatten
78
+ degrees = [2, 4, 16]
79
+ degrees = [4, 16]
80
+ degrees = [16]
81
+ queues = [ CPriorityQueue, PoorPriorityQueue, RubyPriorityQueue ]
82
+ queues = [ CPriorityQueue, RubyPriorityQueue ]
83
+
84
+ max_time = 400
85
+ ignore = Hash.new
86
+
87
+ repeats = 5
88
+
89
+
90
+ STDOUT.sync = true
91
+
92
+ results = Hash.new { | h, k | h[k] =
93
+ Hash.new { | h1, k1 | h1[k1] = Hash.new { 0 }
94
+ }
95
+ }
96
+
97
+ Benchmark.bm(30) do | b |
98
+ sizes.each do | size |
99
+ break if !ignore.empty? and ignore.values.inject(true) { | r, v | r and v }
100
+ puts
101
+ puts "Testing with graphs of size #{size}"
102
+ degrees.each do | degree |
103
+ repeats.times do | r |
104
+ nodes = make_graph(size, degree)
105
+ queues.each do | queue |
106
+ next if ignore[queue]
107
+ GC.start
108
+ results[queue][degree][size] += (b.report("#{queue}: #{size} (#{degree})") do dijkstra(nodes[1], queue) end).real
109
+ end
110
+ end
111
+ queues.each do | queue |
112
+ ignore[queue] ||= ((results[queue][degree][size] / repeats) > max_time)
113
+ end
114
+ end
115
+
116
+ indices = queues.map { | q | degrees.map { | d | %&"#{q} (Graph of Degree: #{d})"& } }.flatten
117
+ File.open("results.csv", "wb") do | f |
118
+ f.puts "size\t" + indices.join("\t")
119
+ sizes.each do | size |
120
+ f.puts "#{size}\t" + queues.map { | q | degrees.map { | d |
121
+ (results[q][d].has_key?(size) and results[q][d][size] > 0.0) ? results[q][d][size] / repeats : "''"
122
+ } }.join("\t")
123
+ end
124
+ end
125
+
126
+ File.open("results.gp", 'wb') do | f |
127
+ lines = []
128
+ indices.each_with_index do | t, i |
129
+ lines << " 'results.csv' using 1:#{i+2} with lines title #{t}"
130
+ end
131
+ f.puts "set term png"
132
+ f.puts "set out 'results.png'"
133
+ f.puts "set xlabel 'Number of nodes'"
134
+ f.puts "set ylabel 'Time in seconds (real)'"
135
+ f.puts "set logscale xy"
136
+ f.puts "set title 'Dijkstras Shortest Path Algorithm using different PQ Implementations'"
137
+ f.puts "plot \\"
138
+ f.puts lines.join(",\\\n")
139
+ end
140
+ system "gnuplot results.gp"
141
+
142
+ queues.each do | q |
143
+ File.open("result-#{q}.gp", 'wb') do | f |
144
+ lines = []
145
+ degrees.map { | d | %&"#{q} (Graph of Degree: #{d})"& }.flatten.each do | t |
146
+ lines << " 'results.csv' using 1:#{indices.index(t)+2} with lines title #{t}"
147
+ end
148
+ f.puts "set term png"
149
+ f.puts "set out 'result-#{q}.png'"
150
+ f.puts "set xlabel 'Number of nodes'"
151
+ f.puts "set ylabel 'Time in seconds (real)'"
152
+ f.puts "set logscale xy"
153
+ f.puts "set title 'Dijkstras Shortest Path Algorithm on Networks of different degrees'"
154
+ f.puts "plot \\"
155
+ f.puts lines.join(",\\\n")
156
+ end
157
+ system "gnuplot result-#{q}.gp"
158
+ end
159
+ end
160
+ end
161
+
162
+ __END__
163
+
164
+ nodes = make_graph(100, 4)
165
+ draw_graph(nodes, "100-4.ps")
166
+ nodes = make_graph(100, 10)
167
+ draw_graph(nodes, "100-10.ps")
168
+ nodes = make_graph(10, 10)
169
+ draw_graph(nodes, "10-10.ps")
170
+ nodes = make_graph(1000, 2)
171
+ draw_graph(nodes, "1000-2.ps")