graphshaper 0.0.1
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/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +32 -0
- data/Rakefile +10 -0
- data/bin/graphshaper +17 -0
- data/graphshaper.gemspec +19 -0
- data/lib/graphshaper.rb +5 -0
- data/lib/graphshaper/undirected_graph.rb +111 -0
- data/lib/graphshaper/version.rb +3 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/undirected_graph_spec.rb +162 -0
- metadata +72 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Lucas Dohmen
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# Graphshaper
|
2
|
+
|
3
|
+
Graphshaper can generate realistic, scale-free graphs of any size. The resulting graph can then be saved into different kinds of databases.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'graphshaper'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install graphshaper
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
The commandline tool expects one argument: The number of nodes you want your generated graph to have. For example:
|
22
|
+
|
23
|
+
graphshaper 50
|
24
|
+
|
25
|
+
## Contributing
|
26
|
+
|
27
|
+
1. Fork it
|
28
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
29
|
+
3. Make your changes – don't forget to spec them with RSpec
|
30
|
+
4. Commit your changes (`git commit -am 'Added some feature'`)
|
31
|
+
5. Push to the branch (`git push origin my-new-feature`)
|
32
|
+
6. Create new Pull Request
|
data/Rakefile
ADDED
data/bin/graphshaper
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'graphshaper'
|
4
|
+
|
5
|
+
if ARGV.length == 0 || ARGV[0] == "--help"
|
6
|
+
puts " Usage: graphshaper SIZE"
|
7
|
+
else
|
8
|
+
inner_nodes = 20
|
9
|
+
number_of_nodes = ARGV[0].to_i
|
10
|
+
|
11
|
+
graph = Graphshaper::UndirectedGraph.without_orphans_with_order_of 20, edge_creation_logger: STDOUT
|
12
|
+
(number_of_nodes - inner_nodes).times do
|
13
|
+
graph.add_vertex do |preferential_attachment|
|
14
|
+
preferential_attachment > rand
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/graphshaper.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/graphshaper/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Lucas Dohmen"]
|
6
|
+
gem.email = ["me@moonglum.net"]
|
7
|
+
gem.description = %q{Graphshaper generates graphs for databases}
|
8
|
+
gem.summary = %q{Graphshaper can generate realistic, scale-free graphs of any size. The resulting graph can then be saved into different kinds of databases.}
|
9
|
+
gem.homepage = "http://github.com/moonglum/graphshaper"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "graphshaper"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Graphshaper::VERSION
|
17
|
+
|
18
|
+
gem.add_development_dependency 'rspec'
|
19
|
+
end
|
data/lib/graphshaper.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
require "set"
|
2
|
+
|
3
|
+
module Graphshaper
|
4
|
+
class UndirectedGraph
|
5
|
+
# Create a graph with a given number of vertices
|
6
|
+
def initialize(number_of_vertices, options_hash = {})
|
7
|
+
@vertex_degrees = [0] * number_of_vertices
|
8
|
+
@edges = Set.new
|
9
|
+
@unconnected_vertices = Set.new (0...number_of_vertices).to_a
|
10
|
+
|
11
|
+
if options_hash.has_key? :edge_creation_logger
|
12
|
+
@edge_creation_logger = options_hash[:edge_creation_logger]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def UndirectedGraph.without_orphans_with_order_of(number_of_vertices, options_hash = {})
|
17
|
+
graph = self.new number_of_vertices, options_hash
|
18
|
+
|
19
|
+
while graph.number_of_orphans > 0
|
20
|
+
a = rand(graph.order)
|
21
|
+
while a == (b = rand(graph.order)); end
|
22
|
+
|
23
|
+
graph.add_edge a, b
|
24
|
+
end
|
25
|
+
|
26
|
+
return graph
|
27
|
+
end
|
28
|
+
|
29
|
+
# the number of vertices
|
30
|
+
def order
|
31
|
+
@vertex_degrees.length
|
32
|
+
end
|
33
|
+
|
34
|
+
# the number of edges
|
35
|
+
def size
|
36
|
+
@edges.length
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_vertex(&block)
|
40
|
+
@vertex_degrees << 0
|
41
|
+
|
42
|
+
new_vertex_id = @vertex_degrees.length - 1
|
43
|
+
if block_given?
|
44
|
+
each_vertex_with_preferential_attachment do |vertex_id, preferential_attachment|
|
45
|
+
add_edge new_vertex_id, vertex_id if block.call(preferential_attachment)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_edge(first_vertex_id, second_vertex_id)
|
51
|
+
if first_vertex_id == second_vertex_id
|
52
|
+
raise "No Self-Referential Edge"
|
53
|
+
elsif first_vertex_id >= order || second_vertex_id >= order
|
54
|
+
raise "ID doesn't exist"
|
55
|
+
else
|
56
|
+
@unconnected_vertices.delete first_vertex_id
|
57
|
+
@vertex_degrees[first_vertex_id] += 1
|
58
|
+
@unconnected_vertices.delete second_vertex_id
|
59
|
+
@vertex_degrees[second_vertex_id] += 1
|
60
|
+
@edges << [first_vertex_id, second_vertex_id].sort
|
61
|
+
|
62
|
+
@edge_creation_logger << "#{first_vertex_id},#{second_vertex_id}\n" if @edge_creation_logger
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def edge_between?(first_vertex_id, second_vertex_id)
|
67
|
+
@edges.include? [first_vertex_id, second_vertex_id]
|
68
|
+
end
|
69
|
+
|
70
|
+
# The number of vertices without edges
|
71
|
+
def number_of_orphans
|
72
|
+
@unconnected_vertices.length
|
73
|
+
end
|
74
|
+
|
75
|
+
def vertex_degree_for(vertex_id)
|
76
|
+
@vertex_degrees[vertex_id]
|
77
|
+
end
|
78
|
+
|
79
|
+
def degree_distribution
|
80
|
+
degree_distribution = []
|
81
|
+
@vertex_degrees.each do |vertex_degree|
|
82
|
+
if degree_distribution[vertex_degree]
|
83
|
+
degree_distribution[vertex_degree] += 1
|
84
|
+
else
|
85
|
+
degree_distribution[vertex_degree] = 1
|
86
|
+
end
|
87
|
+
end
|
88
|
+
degree_distribution
|
89
|
+
end
|
90
|
+
|
91
|
+
def calculate_vertex_degree_for(vertex_id)
|
92
|
+
@vertex_degrees[vertex_id]
|
93
|
+
end
|
94
|
+
|
95
|
+
def sum_of_all_degrees
|
96
|
+
@edges.length * 2
|
97
|
+
end
|
98
|
+
|
99
|
+
def each_vertex_with_preferential_attachment(&block)
|
100
|
+
if sum_of_all_degrees > 0
|
101
|
+
preferential_attachments = @vertex_degrees.map { |degree| degree.round(1) / sum_of_all_degrees }
|
102
|
+
vertex_id = 0
|
103
|
+
preferential_attachments.each do |preferential_attachment|
|
104
|
+
block.call vertex_id, preferential_attachment
|
105
|
+
vertex_id += 1
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "stringio" # for testing the logger
|
3
|
+
|
4
|
+
describe Graphshaper::UndirectedGraph do
|
5
|
+
it "should create a graph with a given number of vertices and no edges" do
|
6
|
+
graph = Graphshaper::UndirectedGraph.new 5
|
7
|
+
graph.order.should ==(5)
|
8
|
+
graph.size.should ==(0)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should create a graph with a logger for edge creation" do
|
12
|
+
edge_creation_logger = StringIO.new
|
13
|
+
graph = Graphshaper::UndirectedGraph.new 5, edge_creation_logger: edge_creation_logger
|
14
|
+
|
15
|
+
graph.add_edge 1,3
|
16
|
+
graph.add_edge 2,3
|
17
|
+
edge_creation_logger.string.should ==("1,3\n2,3\n")
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "initialized graph" do
|
21
|
+
before :each do
|
22
|
+
@graph = Graphshaper::UndirectedGraph.new 5
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should be able to add new vertices" do
|
26
|
+
expect { @graph.add_vertex }.to change{ @graph.order }.by(1)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should add a vertex with two existing ids" do
|
30
|
+
expect { @graph.add_edge 0, 1 }.to change{ @graph.size }.by(1)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "shouldn't add a vertex if one of the ids doesn't exist" do
|
34
|
+
expect { @graph.add_edge 0, 5}.to raise_error(RuntimeError, "ID doesn't exist")
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should answer the question if there is an edge between two vertices with false if they are not" do
|
38
|
+
@graph.edge_between?(0,1).should be_false
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should answer the question if there is an edge between two vertices with true if they are" do
|
42
|
+
@graph.add_edge 0,1
|
43
|
+
@graph.edge_between?(0,1).should be_true
|
44
|
+
end
|
45
|
+
|
46
|
+
it "shouldn't add an edge that has already been added" do
|
47
|
+
@graph.add_edge 0,1
|
48
|
+
expect { @graph.add_edge 0, 1 }.to change{ @graph.size }.by(0)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "shouldn't add an edge that has already been added - independent of direction" do
|
52
|
+
@graph.add_edge 0,1
|
53
|
+
expect { @graph.add_edge 1,0 }.to change{ @graph.size }.by(0)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should not add an edge where the first and second vertex are the same" do
|
57
|
+
expect { @graph.add_edge 0, 0}.to raise_error(RuntimeError, "No Self-Referential Edge")
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should return the graph's order for the number of orphans for a graph without vertices" do
|
61
|
+
@graph.number_of_orphans.should ==(@graph.order)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should return 0 for the number of orphans for a graph connected in a circle" do
|
65
|
+
circle_array = (0...5).to_a
|
66
|
+
circle_array.zip(circle_array.rotate).each do |vertex_a, vertex_b|
|
67
|
+
@graph.add_edge vertex_a, vertex_b
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should calculate the vertex degree" do
|
72
|
+
expect { @graph.add_edge 0,1 }.to change { @graph.vertex_degree_for 1}.by(1)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should calculate the degree distribution" do
|
76
|
+
@graph.degree_distribution.should ==[5]
|
77
|
+
@graph.add_edge 0,1
|
78
|
+
@graph.add_edge 1,2
|
79
|
+
@graph.degree_distribution.should ==[2,2,1]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "random generated graph without orphans" do
|
84
|
+
before :each do
|
85
|
+
@graph = Graphshaper::UndirectedGraph.without_orphans_with_order_of 15
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should have the correct order" do
|
89
|
+
@graph.order.should ==(15)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should have no orphans" do
|
93
|
+
@graph.number_of_orphans.should ==(0)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "calculating the vertex's degree and preferential attachment" do
|
98
|
+
before :each do
|
99
|
+
@graph = Graphshaper::UndirectedGraph.new 5
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should calculate the degree of 0 for every vertex in a graph without edges" do
|
103
|
+
5.times do |vertex_id|
|
104
|
+
@graph.calculate_vertex_degree_for(vertex_id).should ==0
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should calculate the degree for a vertex with two edges" do
|
109
|
+
@graph.add_edge 0,1
|
110
|
+
@graph.add_edge 1,2
|
111
|
+
@graph.calculate_vertex_degree_for(1).should ==2
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should calculate the sum of all degrees" do
|
115
|
+
@graph.add_edge 0,1
|
116
|
+
@graph.add_edge 1,2
|
117
|
+
@graph.sum_of_all_degrees.should ==4
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should provide an iterator for preferential attachments that sums up to 0 for a graph without edges" do
|
121
|
+
sum = 0
|
122
|
+
@graph.each_vertex_with_preferential_attachment do |vertex_id, preferential_attachment|
|
123
|
+
sum += preferential_attachment
|
124
|
+
end
|
125
|
+
sum.should ==0
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should calculate the preferential attachments in a way that their sum is always 1 when there is at least one edge" do
|
129
|
+
sum = 0
|
130
|
+
@graph.add_edge 0,1
|
131
|
+
@graph.each_vertex_with_preferential_attachment do |vertex_id, preferential_attachment|
|
132
|
+
sum += preferential_attachment
|
133
|
+
end
|
134
|
+
sum.should ==1
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should add up the preferential attachments to one even if edges are added in the block" do
|
138
|
+
sum = 0
|
139
|
+
@graph.add_edge 0,1
|
140
|
+
@graph.each_vertex_with_preferential_attachment do |vertex_id, preferential_attachment|
|
141
|
+
@graph.add_edge 1,3 if @graph.size < 2
|
142
|
+
sum += preferential_attachment
|
143
|
+
end
|
144
|
+
sum.should ==1
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should add a vertex to the graph with edges according to preferential attachment" do
|
148
|
+
@graph.add_edge 0,1
|
149
|
+
|
150
|
+
# Two nodes with preferential_attachment of 0.5, all others with 0
|
151
|
+
@graph.add_vertex do |preferential_attachment|
|
152
|
+
preferential_attachment > 0.4
|
153
|
+
end
|
154
|
+
|
155
|
+
# One more node
|
156
|
+
@graph.order.should ==(6)
|
157
|
+
|
158
|
+
# Two additional edges
|
159
|
+
@graph.size.should ==(3)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: graphshaper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Lucas Dohmen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-03 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70255506366760 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70255506366760
|
25
|
+
description: Graphshaper generates graphs for databases
|
26
|
+
email:
|
27
|
+
- me@moonglum.net
|
28
|
+
executables:
|
29
|
+
- graphshaper
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- .gitignore
|
34
|
+
- Gemfile
|
35
|
+
- LICENSE
|
36
|
+
- README.md
|
37
|
+
- Rakefile
|
38
|
+
- bin/graphshaper
|
39
|
+
- graphshaper.gemspec
|
40
|
+
- lib/graphshaper.rb
|
41
|
+
- lib/graphshaper/undirected_graph.rb
|
42
|
+
- lib/graphshaper/version.rb
|
43
|
+
- spec/spec_helper.rb
|
44
|
+
- spec/undirected_graph_spec.rb
|
45
|
+
homepage: http://github.com/moonglum/graphshaper
|
46
|
+
licenses: []
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ! '>='
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
requirements: []
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 1.8.6
|
66
|
+
signing_key:
|
67
|
+
specification_version: 3
|
68
|
+
summary: Graphshaper can generate realistic, scale-free graphs of any size. The resulting
|
69
|
+
graph can then be saved into different kinds of databases.
|
70
|
+
test_files:
|
71
|
+
- spec/spec_helper.rb
|
72
|
+
- spec/undirected_graph_spec.rb
|