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 +1 -2
- data/Gemfile.lock +3 -3
- data/VERSION +1 -1
- data/graph.njae.gemspec +5 -8
- data/lib/graph.njae/edge.rb +19 -4
- data/lib/graph.njae/graph.rb +96 -3
- data/lib/graph.njae/vertex.rb +10 -6
- data/spec/graph/edge_spec.rb +57 -12
- data/spec/graph/graph_spec.rb +405 -2
- data/spec/graph/vertex_spec.rb +21 -1
- metadata +10 -21
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -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
|
+
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.
|
29
|
-
rdoc
|
29
|
+
jeweler (~> 1.8.0)
|
30
30
|
rspec (~> 2.8.0)
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/graph.njae.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "graph.njae"
|
8
|
-
s.version = "0.
|
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-
|
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.
|
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.
|
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.
|
62
|
-
s.add_dependency(%q<rdoc>, [">= 0"])
|
59
|
+
s.add_dependency(%q<jeweler>, ["~> 1.8.0"])
|
63
60
|
end
|
64
61
|
end
|
65
62
|
|
data/lib/graph.njae/edge.rb
CHANGED
@@ -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
|
data/lib/graph.njae/graph.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/graph.njae/vertex.rb
CHANGED
@@ -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
|
data/spec/graph/edge_spec.rb
CHANGED
@@ -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
|
data/spec/graph/graph_spec.rb
CHANGED
@@ -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
|
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
|
data/spec/graph/vertex_spec.rb
CHANGED
@@ -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
|
-
|
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.
|
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-
|
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: &
|
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: *
|
24
|
+
version_requirements: *76981430
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: bundler
|
27
|
-
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: *
|
35
|
+
version_requirements: *76981190
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: jeweler
|
38
|
-
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.
|
43
|
+
version: 1.8.0
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
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:
|
87
|
+
hash: 727460339
|
99
88
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
89
|
none: false
|
101
90
|
requirements:
|