graphshaper 0.0.2 → 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/.travis.yml +6 -0
- data/README.md +5 -3
- data/bin/graphshaper +24 -4
- data/graphshaper.gemspec +3 -1
- data/lib/graphshaper/undirected_graph.rb +84 -21
- data/lib/graphshaper/version.rb +1 -1
- data/spec/undirected_graph_spec.rb +12 -4
- metadata +28 -4
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# Graphshaper
|
2
2
|
|
3
|
-
|
3
|
+
[](http://travis-ci.org/moonglum/graphshaper)
|
4
|
+
|
5
|
+
Graphshaper can generate realistic, scale-free graphs of any size. It is tested with MRI Ruby (1.9.2. and 1.9.3) and the 1.9 versions of jRuby and Rubinius.
|
4
6
|
|
5
7
|
## Installation
|
6
8
|
|
@@ -18,11 +20,11 @@ Or install it yourself as:
|
|
18
20
|
|
19
21
|
## Usage
|
20
22
|
|
21
|
-
The commandline tool expects one argument: The number of
|
23
|
+
The commandline tool expects one argument: The number of vertices you want your generated graph to have. This will result in two CSV files containing your vertices and edges. For example:
|
22
24
|
|
23
25
|
graphshaper 50
|
24
26
|
|
25
|
-
You can also use the
|
27
|
+
You can also use the library in your Ruby Code. You can find the documentation [here](http://rubydoc.info/github/moonglum/graphshaper).
|
26
28
|
|
27
29
|
## Contributing
|
28
30
|
|
data/bin/graphshaper
CHANGED
@@ -4,14 +4,34 @@ require 'graphshaper'
|
|
4
4
|
|
5
5
|
if ARGV.length == 0 || ARGV[0] == "--help"
|
6
6
|
puts " Usage: graphshaper SIZE"
|
7
|
+
elsif ARGV[0].to_i < 21
|
8
|
+
puts "Please choose a size of at least 21"
|
7
9
|
else
|
8
|
-
|
9
|
-
|
10
|
+
number_of_vertices = ARGV[0].to_i
|
11
|
+
inner_vertices = 20
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
+
edge_output_file = File.new "edges.csv", "w"
|
14
|
+
vertex_output_file = File.new "vertices.csv", "w"
|
15
|
+
|
16
|
+
start_time = Time.now
|
17
|
+
graph = Graphshaper::UndirectedGraph.without_orphans_with_order_of inner_vertices, edge_creation_logger: edge_output_file, vertex_creation_logger: vertex_output_file
|
18
|
+
(number_of_vertices - inner_vertices).times do
|
13
19
|
graph.add_vertex do |preferential_attachment|
|
14
20
|
preferential_attachment > rand
|
15
21
|
end
|
16
22
|
end
|
23
|
+
end_time = Time.now
|
24
|
+
|
25
|
+
ellapsed_time = end_time - start_time
|
26
|
+
|
27
|
+
puts "#{graph.order} vertices (saved to vertices.csv)"
|
28
|
+
puts "#{graph.size} edges (saved to edges.csv)"
|
29
|
+
|
30
|
+
if ellapsed_time < 2
|
31
|
+
puts "Generated in about one second"
|
32
|
+
elsif ellapsed_time < 60
|
33
|
+
puts "Generated in about #{ellapsed_time.round} seconds"
|
34
|
+
else
|
35
|
+
puts "Generated in about #{ellapsed_time.round / 60} minutes and #{ellapsed_time.round % 60} seconds"
|
36
|
+
end
|
17
37
|
end
|
data/graphshaper.gemspec
CHANGED
@@ -15,5 +15,7 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
gem.version = Graphshaper::VERSION
|
17
17
|
|
18
|
-
gem.add_development_dependency
|
18
|
+
gem.add_development_dependency "rake", "~> 0.9.2.2"
|
19
|
+
gem.add_development_dependency "rspec", "~> 2.9.0"
|
20
|
+
gem.add_development_dependency "yard", "~> 0.7.5"
|
19
21
|
end
|
@@ -1,52 +1,91 @@
|
|
1
1
|
require "set"
|
2
2
|
|
3
3
|
module Graphshaper
|
4
|
+
|
5
|
+
# A Graph is undirected when its edges do not have a direction.
|
4
6
|
class UndirectedGraph
|
5
|
-
|
7
|
+
|
8
|
+
# Create a graph with a given number of vertices and no edges.
|
9
|
+
#
|
10
|
+
# @param [Integer] number_of_vertices The number of vertices that the generated graph should have
|
11
|
+
# @param [Hash] options_hash The options to create an undirected graph
|
12
|
+
# @option options_hash [IO] :edge_creation_logger An IO object that should log every edge creation in the graph (default: no logging)
|
13
|
+
# @option options_hash [IO] ::vertex_creation_logger An IO object that should log every vertex creation in the graph (default: no logging)
|
6
14
|
def initialize(number_of_vertices, options_hash = {})
|
7
|
-
@
|
8
|
-
@
|
9
|
-
@unconnected_vertices = Set.new (0...number_of_vertices).to_a
|
15
|
+
@edge_creation_logger = options_hash[:edge_creation_logger] if options_hash.has_key? :edge_creation_logger
|
16
|
+
@vertex_creation_logger = options_hash[:vertex_creation_logger] if options_hash.has_key? :vertex_creation_logger
|
10
17
|
|
11
|
-
|
12
|
-
|
13
|
-
|
18
|
+
@vertex_degrees = []
|
19
|
+
@unconnected_vertices = Set.new
|
20
|
+
|
21
|
+
number_of_vertices.times { add_vertex }
|
22
|
+
@edges = Set.new
|
14
23
|
end
|
15
24
|
|
25
|
+
# Create a graph with a given number of vertices. Then it adds random edges until the graph doesn't contain any orphans (vertices without edges).
|
26
|
+
#
|
27
|
+
# @param [Integer] number_of_vertices The number of vertices that the generated graph should have
|
28
|
+
# @param [Hash] options_hash The options to create an undirected graph
|
29
|
+
# @option options_hash [IO] :edge_creation_logger An IO object that should log every edge creation in the graph (default: no logging)
|
30
|
+
# @option options_hash [IO] ::vertex_creation_logger An IO object that should log every vertex creation in the graph (default: no logging)
|
31
|
+
# @return [UndirectedGraph] The generated graph.
|
16
32
|
def UndirectedGraph.without_orphans_with_order_of(number_of_vertices, options_hash = {})
|
17
33
|
graph = self.new number_of_vertices, options_hash
|
18
34
|
|
19
35
|
while graph.number_of_orphans > 0
|
20
|
-
|
21
|
-
while
|
36
|
+
vertex_orphan = graph.orphans.shuffle.first
|
37
|
+
while vertex_orphan == (random_vertex = rand(graph.order)); end
|
22
38
|
|
23
|
-
graph.add_edge
|
39
|
+
graph.add_edge vertex_orphan, random_vertex
|
24
40
|
end
|
25
41
|
|
26
42
|
return graph
|
27
43
|
end
|
28
44
|
|
29
|
-
#
|
45
|
+
# The number of vertices.
|
46
|
+
#
|
47
|
+
# @return [Integer] Number of vertices.
|
30
48
|
def order
|
31
49
|
@vertex_degrees.length
|
32
50
|
end
|
33
51
|
|
34
|
-
#
|
52
|
+
# The number of edges.
|
53
|
+
#
|
54
|
+
# @return [Integer] Number of edges.
|
35
55
|
def size
|
36
56
|
@edges.length
|
37
57
|
end
|
38
58
|
|
59
|
+
# Add a new vertex to the graph.
|
60
|
+
# If you call it with a block {|preferential_attachment| ... }, the block will be called for every existing vertex: An edge from the new vertex to this vertex will be created if and only if the block returns true.
|
61
|
+
# @yield [preferential_attachment] The block that tests if the edge should be added.
|
62
|
+
#
|
63
|
+
# @yieldparam [Float] preferential_attachment The preferential attachment of the existing vertex
|
64
|
+
# @yieldreturn [Boolean] Should the edge be added?
|
65
|
+
#
|
66
|
+
# @return [Integer] ID of the newly created vertex.
|
39
67
|
def add_vertex(&block)
|
68
|
+
new_vertex_id = @vertex_degrees.length
|
69
|
+
|
40
70
|
@vertex_degrees << 0
|
71
|
+
@unconnected_vertices << new_vertex_id
|
72
|
+
|
73
|
+
@vertex_creation_logger << "#{new_vertex_id}\n" if @vertex_creation_logger
|
41
74
|
|
42
|
-
new_vertex_id = @vertex_degrees.length - 1
|
43
75
|
if block_given?
|
44
76
|
each_vertex_with_preferential_attachment do |vertex_id, preferential_attachment|
|
45
77
|
add_edge new_vertex_id, vertex_id if block.call(preferential_attachment)
|
46
78
|
end
|
47
79
|
end
|
80
|
+
|
81
|
+
new_vertex_id
|
48
82
|
end
|
49
83
|
|
84
|
+
# Add a new edge to the graph between two existing vertices.
|
85
|
+
#
|
86
|
+
# @param [Integer] first_vertex_id
|
87
|
+
# @param [Integer] second_vertex_id
|
88
|
+
# @raise [RuntimeError] The method throws a RuntimeError if you try to add a self-referential edge or an edge with a non-existing vertex.
|
50
89
|
def add_edge(first_vertex_id, second_vertex_id)
|
51
90
|
if first_vertex_id == second_vertex_id
|
52
91
|
raise "No Self-Referential Edge"
|
@@ -63,19 +102,40 @@ module Graphshaper
|
|
63
102
|
end
|
64
103
|
end
|
65
104
|
|
105
|
+
# Tests, if an edge between the two vertices exists.
|
106
|
+
#
|
107
|
+
# @param [Integer] first_vertex_id
|
108
|
+
# @param [Integer] second_vertex_id
|
109
|
+
# @return [Boolean] Does the vertex exist?
|
66
110
|
def edge_between?(first_vertex_id, second_vertex_id)
|
67
111
|
@edges.include? [first_vertex_id, second_vertex_id]
|
68
112
|
end
|
69
113
|
|
70
|
-
# The number of vertices without edges
|
114
|
+
# The number of vertices without edges.
|
115
|
+
#
|
116
|
+
# @return [Integer] Number of vertices without edges.
|
71
117
|
def number_of_orphans
|
72
|
-
@unconnected_vertices.length
|
118
|
+
@unconnected_vertices.to_a.length
|
73
119
|
end
|
74
120
|
|
121
|
+
# The vertices without edges as an array.
|
122
|
+
#
|
123
|
+
# @return [Array<Integer>] IDs of the Vertices without edges.
|
124
|
+
def orphans
|
125
|
+
@unconnected_vertices.to_a
|
126
|
+
end
|
127
|
+
|
128
|
+
# Return the vertex degree for the vertex with the given ID.
|
129
|
+
#
|
130
|
+
# @param [Integer] vertex_id
|
131
|
+
# @return [Integer] Degree of the given vertex
|
75
132
|
def vertex_degree_for(vertex_id)
|
76
133
|
@vertex_degrees[vertex_id]
|
77
134
|
end
|
78
135
|
|
136
|
+
# Calculates the distribution of degrees in the graph. The value at the n-th position of the returned array is the number of vertices with n edges.
|
137
|
+
#
|
138
|
+
# @return [Array<Integer>] The degree distribution as an array of Integers.
|
79
139
|
def degree_distribution
|
80
140
|
degree_distribution = []
|
81
141
|
@vertex_degrees.each do |vertex_degree|
|
@@ -87,15 +147,19 @@ module Graphshaper
|
|
87
147
|
end
|
88
148
|
degree_distribution
|
89
149
|
end
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
150
|
+
|
151
|
+
# Return the sum of all degrees.
|
152
|
+
#
|
153
|
+
# @return [Integer] Sum of all degrees
|
95
154
|
def sum_of_all_degrees
|
96
155
|
@edges.length * 2
|
97
156
|
end
|
98
157
|
|
158
|
+
# Iterate over all vertices of the graph. Call it with a block {|vertex_id, preferential_attachment| ... }.
|
159
|
+
#
|
160
|
+
# @yield [vertex_id, preferential_attachment] The block that tests if the edge should be added.
|
161
|
+
# @yieldparam [Integer] vertex_id The preferential attachment of the existing vertex
|
162
|
+
# @yieldparam [Float] preferential_attachment The preferential attachment of the existing vertex
|
99
163
|
def each_vertex_with_preferential_attachment(&block)
|
100
164
|
if sum_of_all_degrees > 0
|
101
165
|
preferential_attachments = @vertex_degrees.map { |degree| degree.round(1) / sum_of_all_degrees }
|
@@ -106,6 +170,5 @@ module Graphshaper
|
|
106
170
|
end
|
107
171
|
end
|
108
172
|
end
|
109
|
-
|
110
173
|
end
|
111
174
|
end
|
data/lib/graphshaper/version.rb
CHANGED
@@ -17,6 +17,14 @@ describe Graphshaper::UndirectedGraph do
|
|
17
17
|
edge_creation_logger.string.should ==("1,3\n2,3\n")
|
18
18
|
end
|
19
19
|
|
20
|
+
it "should create a graph with a logger for vertex creation" do
|
21
|
+
vertex_creation_logger = StringIO.new
|
22
|
+
graph = Graphshaper::UndirectedGraph.new 5, vertex_creation_logger: vertex_creation_logger
|
23
|
+
|
24
|
+
graph.add_vertex
|
25
|
+
vertex_creation_logger.string.should ==("0\n1\n2\n3\n4\n5\n")
|
26
|
+
end
|
27
|
+
|
20
28
|
describe "initialized graph" do
|
21
29
|
before :each do
|
22
30
|
@graph = Graphshaper::UndirectedGraph.new 5
|
@@ -101,14 +109,14 @@ describe Graphshaper::UndirectedGraph do
|
|
101
109
|
|
102
110
|
it "should calculate the degree of 0 for every vertex in a graph without edges" do
|
103
111
|
5.times do |vertex_id|
|
104
|
-
@graph.
|
112
|
+
@graph.vertex_degree_for(vertex_id).should ==0
|
105
113
|
end
|
106
114
|
end
|
107
115
|
|
108
116
|
it "should calculate the degree for a vertex with two edges" do
|
109
117
|
@graph.add_edge 0,1
|
110
118
|
@graph.add_edge 1,2
|
111
|
-
@graph.
|
119
|
+
@graph.vertex_degree_for(1).should ==2
|
112
120
|
end
|
113
121
|
|
114
122
|
it "should calculate the sum of all degrees" do
|
@@ -147,12 +155,12 @@ describe Graphshaper::UndirectedGraph do
|
|
147
155
|
it "should add a vertex to the graph with edges according to preferential attachment" do
|
148
156
|
@graph.add_edge 0,1
|
149
157
|
|
150
|
-
# Two
|
158
|
+
# Two vertices with preferential_attachment of 0.5, all others with 0
|
151
159
|
@graph.add_vertex do |preferential_attachment|
|
152
160
|
preferential_attachment > 0.4
|
153
161
|
end
|
154
162
|
|
155
|
-
# One more
|
163
|
+
# One more vertex
|
156
164
|
@graph.order.should ==(6)
|
157
165
|
|
158
166
|
# Two additional edges
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graphshaper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: '0.1'
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,22 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-04-
|
12
|
+
date: 2012-04-04 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: &70350123480940 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.9.2.2
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70350123480940
|
14
25
|
- !ruby/object:Gem::Dependency
|
15
26
|
name: rspec
|
16
|
-
requirement: &
|
27
|
+
requirement: &70350123480440 !ruby/object:Gem::Requirement
|
17
28
|
none: false
|
18
29
|
requirements:
|
19
30
|
- - ~>
|
@@ -21,7 +32,18 @@ dependencies:
|
|
21
32
|
version: 2.9.0
|
22
33
|
type: :development
|
23
34
|
prerelease: false
|
24
|
-
version_requirements: *
|
35
|
+
version_requirements: *70350123480440
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: yard
|
38
|
+
requirement: &70350123479980 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.7.5
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70350123479980
|
25
47
|
description: Generate realistic graphs
|
26
48
|
email:
|
27
49
|
- me@moonglum.net
|
@@ -31,6 +53,7 @@ extensions: []
|
|
31
53
|
extra_rdoc_files: []
|
32
54
|
files:
|
33
55
|
- .gitignore
|
56
|
+
- .travis.yml
|
34
57
|
- Gemfile
|
35
58
|
- LICENSE
|
36
59
|
- README.md
|
@@ -69,3 +92,4 @@ summary: Graphshaper can generate realistic, scale-free graphs of any size.
|
|
69
92
|
test_files:
|
70
93
|
- spec/spec_helper.rb
|
71
94
|
- spec/undirected_graph_spec.rb
|
95
|
+
has_rdoc:
|