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 ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .rvmrc
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in graphshaper.gemspec
4
+ gemspec
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
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rspec"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new("spec") do |spec|
7
+ spec.pattern = "spec/**/*_spec.rb"
8
+ end
9
+
10
+ task :default => :spec
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
@@ -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
@@ -0,0 +1,5 @@
1
+ require "graphshaper/version"
2
+ require "graphshaper/undirected_graph"
3
+
4
+ module Graphshaper
5
+ end
@@ -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
@@ -0,0 +1,3 @@
1
+ module Graphshaper
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
3
+
4
+ require 'graphshaper'
@@ -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