plexus 0.5.4 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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")