graph.njae 0.2.4 → 0.3.0

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.
data/Gemfile CHANGED
@@ -8,6 +8,5 @@ source "http://rubygems.org"
8
8
  group :development do
9
9
  gem "rspec", "~> 2.8.0"
10
10
  gem "bundler", "~> 1.0.0"
11
- gem "jeweler", "~> 1.6.2"
12
- gem "rdoc"
11
+ gem "jeweler", "~> 1.8.0"
13
12
  end
@@ -3,10 +3,11 @@ GEM
3
3
  specs:
4
4
  diff-lcs (1.1.3)
5
5
  git (1.2.5)
6
- jeweler (1.6.4)
6
+ jeweler (1.8.3)
7
7
  bundler (~> 1.0)
8
8
  git (>= 1.2.5)
9
9
  rake
10
+ rdoc
10
11
  json (1.6.5)
11
12
  rake (0.9.2.2)
12
13
  rdoc (3.12)
@@ -25,6 +26,5 @@ PLATFORMS
25
26
 
26
27
  DEPENDENCIES
27
28
  bundler (~> 1.0.0)
28
- jeweler (~> 1.6.2)
29
- rdoc
29
+ jeweler (~> 1.8.0)
30
30
  rspec (~> 2.8.0)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.4
1
+ 0.3.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "graph.njae"
8
- s.version = "0.2.4"
8
+ s.version = "0.3.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Neil Smith"]
12
- s.date = "2012-03-05"
12
+ s.date = "2012-05-18"
13
13
  s.description = "A simple graph library"
14
14
  s.email = "neil.github@njae.me.uk"
15
15
  s.extra_rdoc_files = [
@@ -47,19 +47,16 @@ Gem::Specification.new do |s|
47
47
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
48
48
  s.add_development_dependency(%q<rspec>, ["~> 2.8.0"])
49
49
  s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
50
- s.add_development_dependency(%q<jeweler>, ["~> 1.6.2"])
51
- s.add_development_dependency(%q<rdoc>, [">= 0"])
50
+ s.add_development_dependency(%q<jeweler>, ["~> 1.8.0"])
52
51
  else
53
52
  s.add_dependency(%q<rspec>, ["~> 2.8.0"])
54
53
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
55
- s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
56
- s.add_dependency(%q<rdoc>, [">= 0"])
54
+ s.add_dependency(%q<jeweler>, ["~> 1.8.0"])
57
55
  end
58
56
  else
59
57
  s.add_dependency(%q<rspec>, ["~> 2.8.0"])
60
58
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
61
- s.add_dependency(%q<jeweler>, ["~> 1.6.2"])
62
- s.add_dependency(%q<rdoc>, [">= 0"])
59
+ s.add_dependency(%q<jeweler>, ["~> 1.8.0"])
63
60
  end
64
61
  end
65
62
 
@@ -10,8 +10,8 @@ module GraphNjae
10
10
  # Each connection is handled by a Graph::Connection object, so that each end
11
11
  # of the Edge can have it's own attributes.
12
12
  class Edge < OpenStruct
13
- def initialize
14
- super
13
+ def initialize(values = {})
14
+ super(values)
15
15
  self.connections = []
16
16
  self
17
17
  end
@@ -20,6 +20,7 @@ module GraphNjae
20
20
  def <<(other)
21
21
  c = Connection.new
22
22
  c.end = other
23
+ other.edges << self unless other.edges.include? self
23
24
  self.connections << c
24
25
  self
25
26
  end
@@ -33,13 +34,27 @@ module GraphNjae
33
34
  def connection_at(vertex)
34
35
  self.connections.find {|c| c.end.equal? vertex}
35
36
  end
37
+
38
+ # Return the vertex at the other end of the one given.
39
+ # Self-loops should still return the vertex
40
+ def other_end(vertex)
41
+ if self.vertices[0] == vertex
42
+ self.vertices[1]
43
+ else
44
+ self.vertices[0]
45
+ end
46
+ end
47
+
48
+ def to_s
49
+ '<E: ' + self.type.to_s + ' [' + self.vertices.map {|n| n.to_s}.join(', ') + '] >'
50
+ end
36
51
  end
37
52
 
38
53
  # A connection between an Edge and a Vertex.The connection can have arbitrary attributes,
39
54
  # treated as method names.
40
55
  class Connection < OpenStruct
41
- def initialize
42
- super
56
+ def initialize(values = {})
57
+ super(values)
43
58
  self
44
59
  end
45
60
  end
@@ -1,5 +1,9 @@
1
1
  require 'ostruct'
2
2
 
3
+ require 'logger'
4
+ $log = Logger.new(STDERR)
5
+ $log.level = Logger::WARN
6
+
3
7
  # A simple graph library
4
8
 
5
9
  module GraphNjae
@@ -7,8 +11,8 @@ module GraphNjae
7
11
  # A container for all the parts of a graph. The graph can have arbitrary attributes,
8
12
  # treated as method names.
9
13
  class Graph < OpenStruct
10
- def initialize
11
- super
14
+ def initialize(values = {})
15
+ super(values)
12
16
  self.edges = Array.new
13
17
  self.vertices = Array.new
14
18
  self
@@ -23,5 +27,94 @@ module GraphNjae
23
27
  end
24
28
  self
25
29
  end
26
- end
30
+
31
+ # Connects two vertices, creating and storing a new edge
32
+ # Also adds the vertices, unless they're already in the graph
33
+ def connect(vertex1, vertex2, edge_attributes = {})
34
+ self.vertices << vertex1 unless self.vertices.include? vertex1
35
+ self.vertices << vertex2 unless self.vertices.include? vertex2
36
+ edge = Edge.new(edge_attributes)
37
+ self.edges << edge
38
+ edge << vertex1 << vertex2
39
+ end
40
+
41
+ # Form a product graph of this graph and the other.
42
+ # Return the product graph.
43
+ def product(other)
44
+ product_graph = Graph.new
45
+ self.vertices.each do |v1|
46
+ other.vertices.each do |v2|
47
+ product_graph << Vertex.new({:g1_vertex => v1, :g2_vertex => v2})
48
+ end
49
+ end
50
+ self.edges.each do |e1|
51
+ e1_vertices = e1.vertices
52
+ other.edges.each do |e2|
53
+ if e1.type == e2.type
54
+ e2_vertices = e2.vertices
55
+ source = product_graph.vertices.find {|v| v.g1_vertex == e1_vertices[0] and v.g2_vertex == e2_vertices[0]}
56
+ destination = product_graph.vertices.find {|v| v.g1_vertex == e1_vertices[1] and v.g2_vertex == e2_vertices[1]}
57
+ product_graph.connect source, destination
58
+ source = product_graph.vertices.find {|v| v.g1_vertex == e1_vertices[0] and v.g2_vertex == e2_vertices[1]}
59
+ destination = product_graph.vertices.find {|v| v.g1_vertex == e1_vertices[1] and v.g2_vertex == e2_vertices[0]}
60
+ product_graph.connect source, destination
61
+ end
62
+ end
63
+ end
64
+ product_graph.vertices.reject! {|v| v.neighbours.empty?}
65
+ product_graph
66
+ end
67
+
68
+
69
+ def initial_similarity
70
+ self.vertices.each do |v|
71
+ if block_given?
72
+ v.initial_similarity = yield v
73
+ else
74
+ v.initial_similarity = 1.0
75
+ end
76
+ v.similarity = v.initial_similarity
77
+ end
78
+ end
79
+
80
+ # Performs similarity flooding on a graph
81
+ # Assumes that the initial similarity has already been calculated
82
+ def similarity_flood(opts = {})
83
+ max_iterations = opts[:iterations] || 100
84
+ max_residual = opts[:max_residual] || 0.001
85
+ iteration = 1
86
+ residual = max_residual + 1
87
+ while residual > max_residual and iteration <= max_iterations
88
+ $log.debug { "Starting iteration #{iteration}" }
89
+ self.vertices.each do |v|
90
+ v.last_similarity = v.similarity
91
+ end
92
+ self.vertices.each do |v|
93
+ if block_given?
94
+ v.similarity = yield v
95
+ else
96
+ $log.debug { "Processing vertex #{v.name}" }
97
+ edge_groups = v.edges.group_by {|e| e.type }
98
+ $log.debug { " Edge groups {#{edge_groups.keys.map {|t| t.to_s + ' => {' + edge_groups[t].map {|e| e.to_s}.join(', ')}.join('; ')}}" }
99
+ edge_groups.each do |type, edges|
100
+ $log.debug { " Processing group type #{type}" }
101
+ n = edges.length
102
+ edges.each do |e|
103
+ e.other_end(v).similarity += v.last_similarity / n
104
+ end
105
+ end
106
+ end
107
+ end
108
+ max_similarity = vertices.map {|v| v.similarity}.max
109
+ self.vertices.each do |v|
110
+ v.similarity = v.similarity / max_similarity
111
+ end
112
+ residual = Math.sqrt(self.vertices.reduce(0) {|a, v| a += (v.similarity - v.last_similarity) ** 2})
113
+ $log.debug { puts "Residual = #{residual.round(3)}, sims = #{self.vertices.map {|v| v.name + " = " + v.similarity.round(2).to_s}}" }
114
+ iteration += 1
115
+ end
116
+
117
+ end
118
+
119
+ end # class
27
120
  end
@@ -6,19 +6,19 @@ module GraphNjae
6
6
  # A vertex in a graph. The edge can have arbitrary attributes,treated as
7
7
  # method names.
8
8
  class Vertex < OpenStruct
9
- def initialize
10
- super
9
+ def initialize(values = {})
10
+ super(values)
11
11
  self.edges = []
12
12
  self
13
13
  end
14
14
 
15
15
  # Connect this vertex to another, creating an Edge to do so, and returning
16
16
  # the Edge
17
- def connect(other)
18
- e = Edge.new
17
+ def connect(other, edge_attributes = {})
18
+ e = Edge.new edge_attributes
19
19
  e << self << other
20
- self.edges << e
21
- other.edges << e unless self === other
20
+ # self.edges << e
21
+ # other.edges << e unless self === other
22
22
  e
23
23
  end
24
24
 
@@ -39,5 +39,9 @@ module GraphNjae
39
39
  e.vertices.drop_while {|v| v != self}[1..-1]}.flatten
40
40
  end
41
41
 
42
+ def to_s
43
+ '<V: ' + self.name + '>'
44
+ end
45
+
42
46
  end
43
47
  end
@@ -8,6 +8,14 @@ module GraphNjae
8
8
  e = Edge.new
9
9
  e.connections.should be_empty
10
10
  end
11
+
12
+ it "creates an edge with some parameters" do
13
+ e = Edge.new :value1 => 1, :value2 => "value2", :value3 => :v3
14
+ e.value1.should == 1
15
+ e.value2.should == "value2"
16
+ e.value3.should == :v3
17
+ e.value4.should be_nil
18
+ end
11
19
  end # #initialize
12
20
 
13
21
  describe "adds attribues" do
@@ -20,23 +28,25 @@ module GraphNjae
20
28
  describe "#<<" do
21
29
  it "adds a new vertex to an edge (with a connection)" do
22
30
  e.connections.should be_empty
23
- v1 = Vertex.new
24
- v2 = Vertex.new
31
+ v1 = Vertex.new :name => :v1
32
+ v2 = Vertex.new :name => :v2
25
33
  e << v1
26
34
  e.should have(1).connections
27
35
  e.should have(1).vertices
28
36
  e.vertices.should include(v1)
37
+ v1.edges.should include(e)
29
38
  e << v2
30
39
  e.should have(2).connections
31
40
  e.should have(2).vertices
32
41
  e.vertices.should include(v1)
33
42
  e.vertices.should include(v2)
43
+ v2.edges.should include(e)
34
44
  end
35
45
 
36
46
  it "adds several vertices to an edge" do
37
47
  e.connections.should be_empty
38
- v1 = Vertex.new
39
- v2 = Vertex.new
48
+ v1 = Vertex.new :name => :v1
49
+ v2 = Vertex.new :name => :v2
40
50
  e << v1 << v2
41
51
  e.vertices.should include(v1)
42
52
  e.vertices.should include(v2)
@@ -56,8 +66,8 @@ module GraphNjae
56
66
  describe "connection_at" do
57
67
  it "returns the connection that links to a vertex" do
58
68
  e.connections.should be_empty
59
- v1 = Vertex.new
60
- v2 = Vertex.new
69
+ v1 = Vertex.new :name => :v1
70
+ v2 = Vertex.new :name => :v2
61
71
  e << v1 << v2
62
72
 
63
73
  e.connection_at(v1).end.should be v1
@@ -66,9 +76,9 @@ module GraphNjae
66
76
 
67
77
  it "returns nil if there is no connection to that vertex" do
68
78
  e.connections.should be_empty
69
- v1 = Vertex.new
70
- v2 = Vertex.new
71
- v3 = Vertex.new
79
+ v1 = Vertex.new :name => :v1
80
+ v2 = Vertex.new :name => :v2
81
+ v3 = Vertex.new :name => :v3
72
82
  e << v1 << v2
73
83
 
74
84
  e.connection_at(v3).should be nil
@@ -76,14 +86,49 @@ module GraphNjae
76
86
 
77
87
  it "returns the vertex for a self-loop" do
78
88
  e.connections.should be_empty
79
- v1 = Vertex.new
89
+ v1 = Vertex.new :name => :v1
80
90
  e << v1 << v1
81
91
 
82
92
  e.connection_at(v1).end.should be v1
83
93
  end
84
-
85
-
86
94
  end # #connection_at
95
+
96
+ describe "other_end" do
97
+ it "returns the vertex at the other end of the given one" do
98
+ e.connections.should be_empty
99
+ v1 = Vertex.new :name => :v1
100
+ v2 = Vertex.new :name => :v2
101
+ e << v1 << v2
102
+
103
+ e.other_end(v1).should be v2
104
+ e.other_end(v2).should be v1
105
+ end
106
+
107
+ it "returns the same vertex in a self-loop" do
108
+ e.connections.should be_empty
109
+ v1 = Vertex.new :name => :v1
110
+ e << v1 << v1
111
+
112
+ e.other_end(v1).should be v1
113
+ end
114
+
115
+ it "returns one of the connected edges if given a vertex not connected to it" do
116
+ e.connections.should be_empty
117
+ v1 = Vertex.new :name => :v1
118
+ v2 = Vertex.new :name => :v2
119
+ v3 = Vertex.new :name => :v3
120
+ e << v1 << v2
121
+
122
+ [v1, v2].should include e.other_end(v3)
123
+ end
124
+
125
+ it "returns nil if it can't return something sensible" do
126
+ v1 = Vertex.new :name => :v1
127
+ e.other_end(v1).should be_nil
128
+ e << v1
129
+ e.other_end(v1).should be_nil
130
+ end
131
+ end # other_end
87
132
  end # Edge
88
133
 
89
134
  describe Connection do
@@ -16,12 +16,21 @@ module GraphNjae
16
16
  g.edges.should be_empty
17
17
  g.vertices.should be_empty
18
18
  end
19
+
20
+ it "creates a graph with some parameters" do
21
+ g = Graph.new :value1 => 1, :value2 => "value2", :value3 => :v3
22
+ g.value1.should == 1
23
+ g.value2.should == "value2"
24
+ g.value3.should == :v3
25
+ g.value4.should be_nil
26
+ end
27
+
19
28
  end # #initialize
20
29
 
21
- describe "adds attribues" do
30
+ describe "adds attributes" do
22
31
  it "adds then reports arbitrary attributes" do
23
32
  g.score = 15
24
- g.score == 15
33
+ g.score.should == 15
25
34
  end
26
35
  end # adds attributes
27
36
 
@@ -75,8 +84,402 @@ module GraphNjae
75
84
 
76
85
  describe "connect" do
77
86
  it "adds and records an edge between vertices" do
87
+ g.vertices.should be_empty
88
+ g.edges.should be_empty
89
+ v1 = Vertex.new(:name => :v1)
90
+ v2 = Vertex.new(:name => :v2)
91
+ g.connect(v1, v2)
92
+
93
+ g.should have(2).vertices
94
+ g.vertices.should include(v1)
95
+ g.vertices.should include(v2)
96
+ g.should have(1).edges
97
+ end
98
+
99
+ it "adds and records an edge with attributes between vertices" do
100
+ g.vertices.should be_empty
101
+ g.edges.should be_empty
102
+ v1 = Vertex.new(:name => :v1)
103
+ v2 = Vertex.new(:name => :v2)
104
+ g.connect(v1, v2, :type => :edge_type)
105
+
106
+ g.should have(2).vertices
107
+ g.vertices.should include(v1)
108
+ g.vertices.should include(v2)
109
+ g.should have(1).edges
110
+ g.edges[0].type.should == :edge_type
78
111
  end
79
112
  end # #connect
80
113
 
114
+ describe "product" do
115
+ it "finds a product graph of a pair of one-vertex graphs" do
116
+ g1 = Graph.new
117
+ g2 = Graph.new
118
+ g1v1 = Vertex.new
119
+ g1 << g1v1
120
+ g2v1 = Vertex.new
121
+ g2 << g2v1
122
+ product = g1.product g2
123
+
124
+ product.vertices.should be_empty
125
+ #product.vertices.first.g1_vertex.should == g1v1
126
+ #product.vertices.first.g2_vertex.should == g2v1
127
+ product.edges.should be_empty
128
+ end
129
+
130
+ it "finds a product graph of a pair of simple graphs" do
131
+ g1 = Graph.new
132
+ g2 = Graph.new
133
+ g1v1 = Vertex.new(:name => :g1v1)
134
+ g1v2 = Vertex.new(:name => :g1v2)
135
+ g1.connect(g1v1, g1v2)
136
+ g2v1 = Vertex.new(:name => :g2v1)
137
+ g2v2 = Vertex.new(:name => :g2v2)
138
+ g2.connect(g2v1, g2v2)
139
+ pg = g1.product g2
140
+
141
+ pg.should have(4).vertices
142
+ pg.should have(2).edges
143
+ end
144
+
145
+ it "finds a product graph of not-quite-simple graph" do
146
+ g1 = Graph.new
147
+ g2 = Graph.new
148
+ g1v1 = Vertex.new(:name => :g1v1)
149
+ g1v2 = Vertex.new(:name => :g1v2)
150
+ g1v3 = Vertex.new(:name => :g1v3)
151
+ g1.connect(g1v1, g1v2)
152
+ g1.connect(g1v2, g1v3)
153
+ g2v1 = Vertex.new(:name => :g2v1)
154
+ g2v2 = Vertex.new(:name => :g2v2)
155
+ g2v3 = Vertex.new(:name => :g2v3)
156
+ g2.connect(g2v1, g2v2)
157
+ g2.connect(g2v2, g2v3)
158
+ pg = g1.product g2
159
+
160
+ pg.should have(9).vertices
161
+ pg.should have(8).edges
162
+ end
163
+
164
+ it "finds a product graph of a simple graph with edge types" do
165
+ g1 = Graph.new
166
+ g2 = Graph.new
167
+ g1v1 = Vertex.new(:name => :g1v1)
168
+ g1v2 = Vertex.new(:name => :g1v2)
169
+ g1v3 = Vertex.new(:name => :g1v3)
170
+ g1.connect(g1v1, g1v2, :type => :t1)
171
+ g1.connect(g1v2, g1v3, :type => :t2)
172
+ g2v1 = Vertex.new(:name => :g2v1)
173
+ g2v2 = Vertex.new(:name => :g2v2)
174
+ g2v3 = Vertex.new(:name => :g2v3)
175
+ g2.connect(g2v1, g2v2, :type => :t1)
176
+ g2.connect(g2v2, g2v3, :type => :t2)
177
+ pg = g1.product g2
178
+
179
+ pg.should have(7).vertices
180
+ pg.should have(4).edges
181
+ end
182
+
183
+ it "finds a product graph of the example graph from paper" do
184
+ pending "implementation of directed graphs as operands of the product graph"
185
+ g1 = Graph.new
186
+ g2 = Graph.new
187
+ a = Vertex.new(:name => :a)
188
+ a1 = Vertex.new(:name => :a1)
189
+ a2 = Vertex.new(:name => :a2)
190
+ g1.connect(a, a1, :type => :l1)
191
+ g1.connect(a, a2, :type => :l1)
192
+ g1.connect(a1, a2, :type => :l2)
193
+ b = Vertex.new(:name => :b)
194
+ b1 = Vertex.new(:name => :b1)
195
+ b2 = Vertex.new(:name => :b2)
196
+ g2.connect(b, b1, :type => :l1)
197
+ g2.connect(b, b2, :type => :l2)
198
+ g2.connect(b1, b2, :type => :l2)
199
+ pg = g1.product g2
200
+
201
+ pg.should have(4).edges
202
+ pg.should have(6).vertices
203
+ end
204
+
205
+
206
+ end #product
207
+
208
+ describe "initial_similarity" do
209
+ before(:each) do
210
+ g1 = Graph.new
211
+ g2 = Graph.new
212
+ g1v1 = Vertex.new(:name => :g1v1)
213
+ g1v2 = Vertex.new(:name => :g1v2)
214
+ g1.connect(g1v1, g1v2)
215
+ g2v1 = Vertex.new(:name => :g2v1)
216
+ g2v2 = Vertex.new(:name => :g2v2)
217
+ g2.connect(g2v1, g2v2)
218
+ @pg = g1.product g2
219
+ end
220
+
221
+ def simple_name_similarity(n1, n2)
222
+ 1 - n1.to_s.codepoints.to_a.delete_if {|c| n2.to_s.codepoints.to_a.include? c}.length / n1.to_s.length.to_f
223
+ end
224
+
225
+ it "should give all nodes an initial similarity of 1 if no block is given" do
226
+ @pg.initial_similarity
227
+ @pg.vertices.each do |v|
228
+ v.initial_similarity.should be_within(0.001).of(1.0)
229
+ v.similarity.should be_within(0.001).of(1.0)
230
+ end
231
+ end
232
+
233
+ it "should give all nodes the similarity as defined by the given block" do
234
+ @pg.initial_similarity {|v| simple_name_similarity v.g1_vertex.name, v.g2_vertex.name}
235
+ @pg.vertices.each do |v|
236
+ v.initial_similarity.should be_within(0.001).of( simple_name_similarity v.g1_vertex.name, v.g2_vertex.name )
237
+ v.similarity.should be_within(0.001).of( simple_name_similarity v.g1_vertex.name, v.g2_vertex.name )
238
+ end
239
+
240
+ end
241
+ end #initial similarity
242
+
243
+ describe "similarity flood" do
244
+ it "similarity floods a graph of two nodes" do
245
+ g1 = Graph.new
246
+ g2 = Graph.new
247
+ g1v1 = Vertex.new(:name => :g1v1)
248
+ g1v2 = Vertex.new(:name => :g1v2)
249
+ g1.connect(g1v1, g1v2)
250
+ g2v1 = Vertex.new(:name => :g2v1)
251
+ g2v2 = Vertex.new(:name => :g2v2)
252
+ g2.connect(g2v1, g2v2)
253
+ pg = g1.product g2
254
+
255
+ pg.initial_similarity
256
+ pg.similarity_flood
257
+ pg.vertices.each do |v|
258
+ v.similarity.should be_within(0.001).of(1.0)
259
+ end
260
+ end
261
+
262
+ it "similarity floods a graph of three nodes, a -- b -- c" do
263
+ g1 = Graph.new
264
+ g2 = Graph.new
265
+ g1v1 = Vertex.new(:name => :g1v1)
266
+ g1v2 = Vertex.new(:name => :g1v2)
267
+ g1v3 = Vertex.new(:name => :g1v3)
268
+ g1.connect(g1v1, g1v2, :type => :t1)
269
+ g1.connect(g1v2, g1v3, :type => :t2)
270
+ g2v1 = Vertex.new(:name => :g2v1)
271
+ g2v2 = Vertex.new(:name => :g2v2)
272
+ g2v3 = Vertex.new(:name => :g2v3)
273
+ g2.connect(g2v1, g2v2, :type => :t1)
274
+ g2.connect(g2v2, g2v3, :type => :t2)
275
+ pg = g1.product g2
276
+
277
+ pg.initial_similarity
278
+ pg.similarity_flood
279
+ expected_similarities = {
280
+ "g1v1:g2v1" => 0.5,
281
+ "g1v1:g2v2" => 0.6666666666666666,
282
+ "g1v2:g2v1" => 0.6666666666666666,
283
+ "g1v2:g2v2" => 1.0,
284
+ "g1v2:g2v3" => 0.6666666666666666,
285
+ "g1v3:g2v2" => 0.6666666666666666,
286
+ "g1v3:g2v3" => 0.5}
287
+ pg.vertices.each do |v|
288
+ name = v.g1_vertex.name.to_s + ':' + v.g2_vertex.name.to_s
289
+ v.similarity.should be_within(0.001).of(expected_similarities[name])
290
+ end
291
+ end
292
+
293
+ it "simialrity floods the sample graph from the paper" do
294
+ pg = Graph.new
295
+ ab = Vertex.new(:name => "a:b")
296
+ a1b1 = Vertex.new(:name => "a1:b1")
297
+ a2b1 = Vertex.new(:name => "a2:b1")
298
+ a1b2 = Vertex.new(:name => "a1:b2")
299
+ a1b = Vertex.new(:name => "a1:b")
300
+ a2b2 = Vertex.new(:name => "a2:b2")
301
+ pg.connect(ab, a1b1, :type => :l1)
302
+ pg.connect(ab, a2b1, :type => :l1)
303
+ pg.connect(a2b1, a1b2, :type => :l2)
304
+ pg.connect(a1b, a2b2, :type => :l2)
305
+ pg.initial_similarity
306
+ pg.similarity_flood
307
+
308
+ expected_similarities = {
309
+ "a:b" => 1.0,
310
+ "a2:b1" => 0.92,
311
+ "a1:b2" => 0.71,
312
+ "a1:b1" => 0.38,
313
+ "a1:b" => 0.0,
314
+ "a2:b2" => 0.0}
315
+ pg.vertices.each do |v|
316
+ v.similarity.should be_within(0.02).of(expected_similarities[v.name])
317
+ end
318
+ end
319
+
320
+ it "similarity floods a graph of three nodes, a -- b -- c, given a block that performs the default update" do
321
+ g1 = Graph.new
322
+ g2 = Graph.new
323
+ g1v1 = Vertex.new(:name => :g1v1)
324
+ g1v2 = Vertex.new(:name => :g1v2)
325
+ g1v3 = Vertex.new(:name => :g1v3)
326
+ g1.connect(g1v1, g1v2, :type => :t1)
327
+ g1.connect(g1v2, g1v3, :type => :t2)
328
+ g2v1 = Vertex.new(:name => :g2v1)
329
+ g2v2 = Vertex.new(:name => :g2v2)
330
+ g2v3 = Vertex.new(:name => :g2v3)
331
+ g2.connect(g2v1, g2v2, :type => :t1)
332
+ g2.connect(g2v2, g2v3, :type => :t2)
333
+ pg = g1.product g2
334
+
335
+ pg.initial_similarity
336
+ pg.similarity_flood do |v|
337
+ edge_groups = v.edges.group_by {|e| e.type }
338
+ edge_groups.each do |type, edges|
339
+ n = edges.length
340
+ edges.each do |e|
341
+ e.other_end(v).similarity += v.last_similarity / n
342
+ end
343
+ end
344
+ v.similarity
345
+ end
346
+ expected_similarities = {
347
+ "g1v1:g2v1" => 0.5,
348
+ "g1v1:g2v2" => 0.6666666666666666,
349
+ "g1v2:g2v1" => 0.6666666666666666,
350
+ "g1v2:g2v2" => 1.0,
351
+ "g1v2:g2v3" => 0.6666666666666666,
352
+ "g1v3:g2v2" => 0.6666666666666666,
353
+ "g1v3:g2v3" => 0.5}
354
+ pg.vertices.each do |v|
355
+ name = v.g1_vertex.name.to_s + ':' + v.g2_vertex.name.to_s
356
+ v.similarity.should be_within(0.001).of(expected_similarities[name])
357
+ end
358
+ end
359
+
360
+ it "similarity floods a graph of three nodes, a -- b -- c, given a block that performs a different update (method A)" do
361
+ g1 = Graph.new
362
+ g2 = Graph.new
363
+ g1v1 = Vertex.new(:name => :g1v1)
364
+ g1v2 = Vertex.new(:name => :g1v2)
365
+ g1v3 = Vertex.new(:name => :g1v3)
366
+ g1.connect(g1v1, g1v2, :type => :t1)
367
+ g1.connect(g1v2, g1v3, :type => :t2)
368
+ g2v1 = Vertex.new(:name => :g2v1)
369
+ g2v2 = Vertex.new(:name => :g2v2)
370
+ g2v3 = Vertex.new(:name => :g2v3)
371
+ g2.connect(g2v1, g2v2, :type => :t1)
372
+ g2.connect(g2v2, g2v3, :type => :t2)
373
+ pg = g1.product g2
374
+
375
+ pg.initial_similarity
376
+ pg.similarity_flood do |v|
377
+ v.similarity = v.initial_similarity
378
+ edge_groups = v.edges.group_by {|e| e.type }
379
+ edge_groups.each do |type, edges|
380
+ n = edges.length
381
+ edges.each do |e|
382
+ e.other_end(v).similarity += v.last_similarity / n
383
+ end
384
+ end
385
+ v.similarity
386
+ end
387
+ expected_similarities = {
388
+ "g1v1:g2v1" => 0.9269662921348315,
389
+ "g1v1:g2v2" => 1.0,
390
+ "g1v2:g2v1" => 0.6179775280898876,
391
+ "g1v2:g2v2" => 1.0,
392
+ "g1v2:g2v3" => 1.0,
393
+ "g1v3:g2v2" => 0.6179775280898876,
394
+ "g1v3:g2v3" => 0.6179775280898876}
395
+ pg.vertices.each do |v|
396
+ name = v.g1_vertex.name.to_s + ':' + v.g2_vertex.name.to_s
397
+ v.similarity.should be_within(0.001).of(expected_similarities[name])
398
+ end
399
+ end
400
+
401
+ it "similarity floods a graph of three nodes, a -- b -- c, given a block that performs a different update (method B)" do
402
+ g1 = Graph.new
403
+ g2 = Graph.new
404
+ g1v1 = Vertex.new(:name => :g1v1)
405
+ g1v2 = Vertex.new(:name => :g1v2)
406
+ g1v3 = Vertex.new(:name => :g1v3)
407
+ g1.connect(g1v1, g1v2, :type => :t1)
408
+ g1.connect(g1v2, g1v3, :type => :t2)
409
+ g2v1 = Vertex.new(:name => :g2v1)
410
+ g2v2 = Vertex.new(:name => :g2v2)
411
+ g2v3 = Vertex.new(:name => :g2v3)
412
+ g2.connect(g2v1, g2v2, :type => :t1)
413
+ g2.connect(g2v2, g2v3, :type => :t2)
414
+ pg = g1.product g2
415
+
416
+ pg.initial_similarity
417
+ pg.similarity_flood do |v|
418
+ v.similarity = 0.0
419
+ edge_groups = v.edges.group_by {|e| e.type }
420
+ edge_groups.each do |type, edges|
421
+ n = edges.length
422
+ edges.each do |e|
423
+ e.other_end(v).similarity += (v.initial_similarity + v.last_similarity) / n
424
+ end
425
+ end
426
+ v.similarity
427
+ end
428
+ expected_similarities = {
429
+ "g1v1:g2v1" => 1.0,
430
+ "g1v1:g2v2" => 1.0,
431
+ "g1v2:g2v1" => 0.0,
432
+ "g1v2:g2v2" => 1.0,
433
+ "g1v2:g2v3" => 1.0,
434
+ "g1v3:g2v2" => 0.0,
435
+ "g1v3:g2v3" => 0.0}
436
+ pg.vertices.each do |v|
437
+ name = v.g1_vertex.name.to_s + ':' + v.g2_vertex.name.to_s
438
+ v.similarity.should be_within(0.001).of(expected_similarities[name])
439
+ end
440
+ end
441
+
442
+ it "similarity floods a graph of three nodes, a -- b -- c, given a block that performs a different update (method C)" do
443
+ g1 = Graph.new
444
+ g2 = Graph.new
445
+ g1v1 = Vertex.new(:name => :g1v1)
446
+ g1v2 = Vertex.new(:name => :g1v2)
447
+ g1v3 = Vertex.new(:name => :g1v3)
448
+ g1.connect(g1v1, g1v2, :type => :t1)
449
+ g1.connect(g1v2, g1v3, :type => :t2)
450
+ g2v1 = Vertex.new(:name => :g2v1)
451
+ g2v2 = Vertex.new(:name => :g2v2)
452
+ g2v3 = Vertex.new(:name => :g2v3)
453
+ g2.connect(g2v1, g2v2, :type => :t1)
454
+ g2.connect(g2v2, g2v3, :type => :t2)
455
+ pg = g1.product g2
456
+
457
+ pg.initial_similarity
458
+ pg.similarity_flood do |v|
459
+ v.similarity = v.initial_similarity + v.last_similarity
460
+ edge_groups = v.edges.group_by {|e| e.type }
461
+ edge_groups.each do |type, edges|
462
+ n = edges.length
463
+ edges.each do |e|
464
+ e.other_end(v).similarity += (v.initial_similarity + v.last_similarity) / n
465
+ end
466
+ end
467
+ v.similarity
468
+ end
469
+ expected_similarities = {
470
+ "g1v1:g2v1" => 0.8282781862745098,
471
+ "g1v1:g2v2" => 1.0,
472
+ "g1v2:g2v1" => 0.41421568627450983,
473
+ "g1v2:g2v2" => 1.0,
474
+ "g1v2:g2v3" => 1.0,
475
+ "g1v3:g2v2" => 0.41421568627450983,
476
+ "g1v3:g2v3" => 0.41421568627450983}
477
+ pg.vertices.each do |v|
478
+ name = v.g1_vertex.name.to_s + ':' + v.g2_vertex.name.to_s
479
+ v.similarity.should be_within(0.001).of(expected_similarities[name])
480
+ end
481
+ end
482
+ end # similarity_flood
483
+
81
484
  end
82
485
  end
@@ -9,6 +9,14 @@ module GraphNjae
9
9
  v = Vertex.new
10
10
  v.edges.should be_empty
11
11
  end
12
+
13
+ it "creates a vertex with some parameters" do
14
+ v = Vertex.new :value1 => 1, :value2 => "value2", :value3 => :v3
15
+ v.value1.should == 1
16
+ v.value2.should == "value2"
17
+ v.value3.should == :v3
18
+ v.value4.should be_nil
19
+ end
12
20
  end # #initialize
13
21
 
14
22
  describe "adds attribues" do
@@ -77,9 +85,16 @@ module GraphNjae
77
85
  e.vertices.should include(v)
78
86
  e.vertices.should include(v1)
79
87
 
80
- e.should have(2).connections
88
+ e.should have(2).connections
81
89
  end
82
90
 
91
+ it "connects two vertices by an edge with attributes" do
92
+ v1 = Vertex.new
93
+ v1.id = :v1
94
+ e = v.connect(v1, {:type => :edge_type})
95
+ e.type.should == :edge_type
96
+ end
97
+
83
98
  it "creates a self-connection" do
84
99
  e = v.connect v
85
100
 
@@ -96,6 +111,11 @@ module GraphNjae
96
111
  e.should have(2).connections
97
112
  end
98
113
 
114
+ it "creates a self-connection with an edge with attributes" do
115
+ e = v.connect(v, {:type => :edge_type})
116
+ e.type.should == :edge_type
117
+ end
118
+
99
119
  end # #connect
100
120
 
101
121
  describe "#neighbours" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graph.njae
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-05 00:00:00.000000000 Z
12
+ date: 2012-05-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &85433160 !ruby/object:Gem::Requirement
16
+ requirement: &76981430 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 2.8.0
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *85433160
24
+ version_requirements: *76981430
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: bundler
27
- requirement: &85432740 !ruby/object:Gem::Requirement
27
+ requirement: &76981190 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,29 +32,18 @@ dependencies:
32
32
  version: 1.0.0
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *85432740
35
+ version_requirements: *76981190
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: jeweler
38
- requirement: &85432370 !ruby/object:Gem::Requirement
38
+ requirement: &76980930 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
42
42
  - !ruby/object:Gem::Version
43
- version: 1.6.2
43
+ version: 1.8.0
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *85432370
47
- - !ruby/object:Gem::Dependency
48
- name: rdoc
49
- requirement: &85431930 !ruby/object:Gem::Requirement
50
- none: false
51
- requirements:
52
- - - ! '>='
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- type: :development
56
- prerelease: false
57
- version_requirements: *85431930
46
+ version_requirements: *76980930
58
47
  description: A simple graph library
59
48
  email: neil.github@njae.me.uk
60
49
  executables: []
@@ -95,7 +84,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
95
84
  version: '0'
96
85
  segments:
97
86
  - 0
98
- hash: 841005965
87
+ hash: 727460339
99
88
  required_rubygems_version: !ruby/object:Gem::Requirement
100
89
  none: false
101
90
  requirements: