rgraphum 0.0.1.alpha
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 +26 -0
- data/GLOSSARIES.md +108 -0
- data/GREMLIN.md +1398 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +136 -0
- data/Rakefile +16 -0
- data/bin/.irbrc +41 -0
- data/bin/rgraphum_console +61 -0
- data/bin/rgraphum_runner +57 -0
- data/examples/ba_model/make.rb +19 -0
- data/examples/ba_model/make_dummy_twitter_rt_data.rb +0 -0
- data/examples/basic/check_modularity.rb +27 -0
- data/examples/basic/make_graph.rb +12 -0
- data/examples/parser/dot.rb +28 -0
- data/examples/sis_model/lifegame.rb +161 -0
- data/graph_struct.jpg +0 -0
- data/lib/rgraphum/analyzer/linear_regression.rb +31 -0
- data/lib/rgraphum/analyzer/meme_tracker.rb +296 -0
- data/lib/rgraphum/analyzer/twitter/rt_at_mark.rb +45 -0
- data/lib/rgraphum/analyzer.rb +8 -0
- data/lib/rgraphum/cluster.rb +67 -0
- data/lib/rgraphum/communities.rb +65 -0
- data/lib/rgraphum/community.rb +86 -0
- data/lib/rgraphum/cosine_similarity_matrix.rb +40 -0
- data/lib/rgraphum/edge.rb +194 -0
- data/lib/rgraphum/edges.rb +161 -0
- data/lib/rgraphum/ext/cosine_similarity_matrix.rb +79 -0
- data/lib/rgraphum/ext/linear_regression.rb +22 -0
- data/lib/rgraphum/ext/tf_idf.rb +52 -0
- data/lib/rgraphum/graph/gremlin.rb +193 -0
- data/lib/rgraphum/graph/math/clustering_coefficient.rb +53 -0
- data/lib/rgraphum/graph/math/community_detection.rb +141 -0
- data/lib/rgraphum/graph/math/degree_distribution.rb +50 -0
- data/lib/rgraphum/graph/math/dijkstra.rb +331 -0
- data/lib/rgraphum/graph/math.rb +45 -0
- data/lib/rgraphum/graph.rb +267 -0
- data/lib/rgraphum/importer.rb +97 -0
- data/lib/rgraphum/marshal.rb +26 -0
- data/lib/rgraphum/motifs.rb +8 -0
- data/lib/rgraphum/parsers/flare.rb +42 -0
- data/lib/rgraphum/parsers/gephi.rb +193 -0
- data/lib/rgraphum/parsers/graphviz.rb +78 -0
- data/lib/rgraphum/parsers/miserables.rb +54 -0
- data/lib/rgraphum/parsers.rb +32 -0
- data/lib/rgraphum/path.rb +37 -0
- data/lib/rgraphum/query.rb +130 -0
- data/lib/rgraphum/rgraphum_array.rb +159 -0
- data/lib/rgraphum/rgraphum_array_dividers.rb +43 -0
- data/lib/rgraphum/rgraphum_random.rb +5 -0
- data/lib/rgraphum/simulator/ba_model.rb +140 -0
- data/lib/rgraphum/simulator/sir_model.rb +178 -0
- data/lib/rgraphum/simulator/sis_model.rb +158 -0
- data/lib/rgraphum/simulator.rb +29 -0
- data/lib/rgraphum/statistic/power_law.rb +9 -0
- data/lib/rgraphum/t.rb +12 -0
- data/lib/rgraphum/tf_idf.rb +27 -0
- data/lib/rgraphum/version.rb +3 -0
- data/lib/rgraphum/vertex.rb +354 -0
- data/lib/rgraphum/vertices.rb +97 -0
- data/lib/rgraphum.rb +38 -0
- data/performance/add-vertices-edges.rb +20 -0
- data/performance/add-vertices.rb +12 -0
- data/performance/build-graph.rb +19 -0
- data/performance/delete-graph.rb +24 -0
- data/performance/delete-vertices.rb +25 -0
- data/performance/refer-graph.rb +23 -0
- data/rgraphum.gemspec +30 -0
- data/test/lib/rgraphum/analyzer/linear_regression_test.rb +20 -0
- data/test/lib/rgraphum/analyzer/meme_tracker_test.rb +383 -0
- data/test/lib/rgraphum/analyzer/twitter/rt_at_mark_test.rb +120 -0
- data/test/lib/rgraphum/array_test.rb +95 -0
- data/test/lib/rgraphum/bubble_test.rb +7 -0
- data/test/lib/rgraphum/communities_test.rb +53 -0
- data/test/lib/rgraphum/cosine_similarity_test.rb +18 -0
- data/test/lib/rgraphum/edge_test.rb +89 -0
- data/test/lib/rgraphum/edges_test.rb +178 -0
- data/test/lib/rgraphum/graph_builder_test.rb +64 -0
- data/test/lib/rgraphum/graph_dup_test.rb +199 -0
- data/test/lib/rgraphum/graph_plus_test.rb +80 -0
- data/test/lib/rgraphum/graph_test.rb +512 -0
- data/test/lib/rgraphum/gremlin_test.rb +145 -0
- data/test/lib/rgraphum/importers/idg_json_edges.json +20 -0
- data/test/lib/rgraphum/importers/idg_json_test.rb +207 -0
- data/test/lib/rgraphum/importers/idg_json_vertices.json +46 -0
- data/test/lib/rgraphum/math/average_distance_matrix_test.rb +142 -0
- data/test/lib/rgraphum/math/clustering_coefficient_test.rb +219 -0
- data/test/lib/rgraphum/math/community_test.rb +78 -0
- data/test/lib/rgraphum/math/degree_distribution_test.rb +40 -0
- data/test/lib/rgraphum/math/dijkstra_test.rb +146 -0
- data/test/lib/rgraphum/math/modularity_test.rb +154 -0
- data/test/lib/rgraphum/math/quick_average_distance_matrix_test.rb +84 -0
- data/test/lib/rgraphum/path_test.rb +44 -0
- data/test/lib/rgraphum/query/enumerable_test.rb +42 -0
- data/test/lib/rgraphum/query/where_operators_test.rb +75 -0
- data/test/lib/rgraphum/query/where_test.rb +59 -0
- data/test/lib/rgraphum/simulator/ba_model_test.rb +75 -0
- data/test/lib/rgraphum/simulator/sir_model_test.rb +513 -0
- data/test/lib/rgraphum/simulator/sis_model_test.rb +478 -0
- data/test/lib/rgraphum/simulator_test.rb +22 -0
- data/test/lib/rgraphum/tf_idf_test.rb +30 -0
- data/test/lib/rgraphum/vertex_test.rb +50 -0
- data/test/lib/rgraphum/vertices_test.rb +180 -0
- data/test/test_helper.rb +98 -0
- data/tmp/.gitkeep +0 -0
- metadata +254 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# FIXME some 'edge.source's are edge.source.id
|
|
4
|
+
# FIXME some 'edge.target's are edge.target.id
|
|
5
|
+
|
|
6
|
+
class Rgraphum::Graph
|
|
7
|
+
RGRAPHUM = Rgraphum
|
|
8
|
+
|
|
9
|
+
include Rgraphum::Graph::Math
|
|
10
|
+
include Rgraphum::Graph::Gremlin
|
|
11
|
+
include Rgraphum::Marshal
|
|
12
|
+
include Rgraphum::Simulator
|
|
13
|
+
include Rgraphum::Importer
|
|
14
|
+
include Rgraphum::Parsers
|
|
15
|
+
|
|
16
|
+
attr_accessor :aspect
|
|
17
|
+
attr_accessor :label
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
|
|
21
|
+
def build_from_adjacency_matrix( matrix, vertex_labels = [], options={} )
|
|
22
|
+
options = { loop: false, limit: 0.65 }.merge(options)
|
|
23
|
+
|
|
24
|
+
graph = new
|
|
25
|
+
if vertex_labels.size == matrix.size
|
|
26
|
+
vertex_labels.each do |label|
|
|
27
|
+
v = graph.vertices.build(label:label)
|
|
28
|
+
end
|
|
29
|
+
else
|
|
30
|
+
matrix.size.times do
|
|
31
|
+
graph.vertices.build
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
matrix.each_with_index do | row,row_index|
|
|
36
|
+
row.each_with_index do |weight,col_index|
|
|
37
|
+
next if col_index == row_index and !options[:loop]
|
|
38
|
+
|
|
39
|
+
if weight and weight >= options[:limit]
|
|
40
|
+
graph.edges.build( {source:graph.vertices[row_index], target:graph.vertices[col_index],weight:weight} )
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
graph
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @param [Hash] options
|
|
50
|
+
# @option options [Rgraphum::Vertices] :vertices
|
|
51
|
+
# @option options [Rgraphum::Edges] :edges
|
|
52
|
+
#
|
|
53
|
+
def initialize(options={})
|
|
54
|
+
@vertices = Rgraphum::Vertices.new
|
|
55
|
+
if options[:vertices]
|
|
56
|
+
self.vertices = options[:vertices]
|
|
57
|
+
else
|
|
58
|
+
@vertices.graph = self
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
@edges = Rgraphum::Edges.new
|
|
62
|
+
if options[:edges]
|
|
63
|
+
@edges.graph = self
|
|
64
|
+
self.edges = options[:edges]
|
|
65
|
+
else
|
|
66
|
+
@edges.graph = self
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
@aspect = "real"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# 再計算が必要な物にnilをセットする。
|
|
74
|
+
def clear_cache
|
|
75
|
+
@m = nil
|
|
76
|
+
@m_with_weight = nil
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# m is size of edges
|
|
80
|
+
def m
|
|
81
|
+
@m ||= @edges.size
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# degree
|
|
85
|
+
def degree
|
|
86
|
+
self.m * 2
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# m with weight
|
|
90
|
+
def m_with_weight
|
|
91
|
+
@m_with_weight ||= @edges.inject(0.0) { |sum, edge| sum + edge.weight }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# average degree
|
|
95
|
+
def average_degree
|
|
96
|
+
@average_degree ||= (2 * @m / @vertices.size)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def average_path_length
|
|
100
|
+
raise NotImplementedError
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# basic method
|
|
104
|
+
def vertices
|
|
105
|
+
@vertices
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def vertices=(vertex_array)
|
|
109
|
+
@vertices = @vertices.substitute(vertex_array) do |vertex|
|
|
110
|
+
Rgraphum::Vertex(vertex)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def edges
|
|
115
|
+
@edges
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def edges=(edge_array)
|
|
119
|
+
@edges = @edges.substitute(edge_array) do |edge|
|
|
120
|
+
Rgraphum::Edge(edge)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# to_real mean edge has vertex pointer
|
|
125
|
+
def real_aspect!
|
|
126
|
+
return self if @aspect == "real"
|
|
127
|
+
|
|
128
|
+
edges.each do |edge|
|
|
129
|
+
edge.source = @vertices.find_by_id(edge.source) # FIXME
|
|
130
|
+
edge.target = @vertices.find_by_id(edge.target) # FIXME
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
@aspect = "real"
|
|
134
|
+
self
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# to_id mean edge has vertex id
|
|
138
|
+
def id_aspect!
|
|
139
|
+
return self if @aspect == "id"
|
|
140
|
+
|
|
141
|
+
edges.each do |edge|
|
|
142
|
+
edge.source = edge.source.id
|
|
143
|
+
edge.target = edge.target.id
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
@aspect = "id"
|
|
147
|
+
self
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# pickup start vertices
|
|
151
|
+
# it mean pick vertices having no in degree
|
|
152
|
+
def start_root_vertices
|
|
153
|
+
vertices.select do |vertex|
|
|
154
|
+
vertex.inE.empty?
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# pickup end vertices
|
|
159
|
+
# it mean pick vertices having no out degree
|
|
160
|
+
def end_root_vertices
|
|
161
|
+
vertices.select do |vertex|
|
|
162
|
+
vertex.outE.empty?
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def dup
|
|
167
|
+
new_graph = Rgraphum::Graph.new
|
|
168
|
+
|
|
169
|
+
@vertices.each do |vertex|
|
|
170
|
+
new_vertex = vertex.dup
|
|
171
|
+
new_vertex.edges = Rgraphum::Edges.new
|
|
172
|
+
new_graph.vertices << new_vertex
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
@edges.each do |edge|
|
|
176
|
+
new_graph.edges << edge.dup
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
new_graph
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def +(other)
|
|
183
|
+
new_graph = Rgraphum::Graph.new
|
|
184
|
+
start_vertex_id = @vertices.id.max
|
|
185
|
+
start_edge_id = @edges.id.max
|
|
186
|
+
|
|
187
|
+
other_dup = other.dup
|
|
188
|
+
other_dup.vertices.each do | vertex |
|
|
189
|
+
vertex.id = vertex.id + start_vertex_id
|
|
190
|
+
end
|
|
191
|
+
other_dup.edges.each do |edge|
|
|
192
|
+
edge.id = edge.id + start_edge_id
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
new_graph.vertices = self.dup.vertices + other_dup.vertices
|
|
196
|
+
new_graph.edges = self.dup.edges + other_dup.edges
|
|
197
|
+
|
|
198
|
+
new_graph
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def compact_with_label(options={})
|
|
202
|
+
compact_with(:label, self, options)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def marge_with_label(target)
|
|
206
|
+
new_graph = self + target
|
|
207
|
+
new_graph.compact_with_label
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def divide_by_time(interval=20)
|
|
211
|
+
@vertices.divide_by_time(interval)
|
|
212
|
+
@edges.divide_by_time(interval)
|
|
213
|
+
|
|
214
|
+
new_edges = Rgraphum::Edges.new
|
|
215
|
+
new_edges.graph = self
|
|
216
|
+
@edges.each do |edge|
|
|
217
|
+
conditions = { source: edge.source, target: edge.target, start: edge.start }
|
|
218
|
+
same_edge = new_edges.where(conditions).first
|
|
219
|
+
if same_edge
|
|
220
|
+
same_edge.weight += edge.weight
|
|
221
|
+
else
|
|
222
|
+
new_edges << edge
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
self.edges = new_edges
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def compact_with(method_name, graph=self, options ={})
|
|
229
|
+
new_vertices = Rgraphum::Vertices.new
|
|
230
|
+
new_vertices.graph = graph
|
|
231
|
+
graph.vertices.each do |vertex|
|
|
232
|
+
same_vertex = new_vertices.find{ |v| v.send(method_name) == vertex.send(method_name) }
|
|
233
|
+
unless same_vertex
|
|
234
|
+
new_vertex = vertex.dup
|
|
235
|
+
new_vertex.edges = Rgraphum::Edges.new
|
|
236
|
+
new_vertices << new_vertex
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
new_edges = Rgraphum::Edges.new
|
|
241
|
+
graph.edges.each do |edge|
|
|
242
|
+
source_label = edge.source.send(method_name)
|
|
243
|
+
target_label = edge.target.send(method_name)
|
|
244
|
+
edge.source = new_vertices.find{ |vertex| vertex.send(method_name) == source_label }
|
|
245
|
+
edge.target = new_vertices.find{ |vertex| vertex.send(method_name) == target_label }
|
|
246
|
+
|
|
247
|
+
same_edge = new_edges.find{ |e| e.source.equal?(edge.source) and e.source.equal?(edge.source) }
|
|
248
|
+
if same_edge
|
|
249
|
+
same_edge.weight += edge.weight
|
|
250
|
+
else
|
|
251
|
+
new_edges << edge
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
graph.vertices = new_vertices
|
|
256
|
+
graph.edges = new_edges
|
|
257
|
+
graph
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def ==(other)
|
|
261
|
+
return false unless aspect == other.aspect
|
|
262
|
+
return false unless label == other.label
|
|
263
|
+
return false unless vertices == other.vertices
|
|
264
|
+
return false unless edges == other.edges
|
|
265
|
+
true
|
|
266
|
+
end
|
|
267
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
module Rgraphum
|
|
4
|
+
module Importer
|
|
5
|
+
def self.included(base)
|
|
6
|
+
base.extend ClassMethods
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module ClassMethods
|
|
10
|
+
# Load graph from files
|
|
11
|
+
#
|
|
12
|
+
#
|
|
13
|
+
def load(options={})
|
|
14
|
+
parse_options = options.dup
|
|
15
|
+
parse_options[:vertices] &&= open(parse_options[:vertices])
|
|
16
|
+
parse_options[:edges] &&= open(parse_options[:edges])
|
|
17
|
+
parse_options[:path] &&= open(parse_options[:path])
|
|
18
|
+
parse_options[:options] = options[:options]
|
|
19
|
+
parse parse_options
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Parse str and load graph
|
|
23
|
+
#
|
|
24
|
+
#
|
|
25
|
+
def parse(options={})
|
|
26
|
+
graph = Rgraphum::Graph.new
|
|
27
|
+
|
|
28
|
+
case options[:format]
|
|
29
|
+
when :idg_json
|
|
30
|
+
build_graph_from_idg_json graph, options[:vertices], options[:edges], (options[:options] || {})
|
|
31
|
+
when :dump
|
|
32
|
+
graph = load_from(options[:path])
|
|
33
|
+
else
|
|
34
|
+
raise ArgumentError, "Rgraphum::Importer::ClassMethods.parse: Unknown format: '#{options[:format]}'"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
graph
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def build_graph_from_idg_json(graph, vertices_json_str_or_stream, edges_json_str_or_stream, options={})
|
|
43
|
+
vertex_id_hash = {}
|
|
44
|
+
community_hash = {}
|
|
45
|
+
verbose = options[:verbose]
|
|
46
|
+
|
|
47
|
+
puts "Loading vertices ... #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}" if verbose
|
|
48
|
+
$stdout.flush if verbose
|
|
49
|
+
if vertices_json_str_or_stream
|
|
50
|
+
puts "JSON.load start ... #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}" if verbose
|
|
51
|
+
json = JSON.load(vertices_json_str_or_stream)
|
|
52
|
+
puts "JSON.load end ... #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}" if verbose
|
|
53
|
+
json["result"].each_with_index do |vertex_hash, i|
|
|
54
|
+
vertex_id_hash[vertex_hash["rid"]] ||= vertex_id_hash.size
|
|
55
|
+
community_hash[vertex_hash["c_id"]] ||= graph.communities.build
|
|
56
|
+
params = {
|
|
57
|
+
id: vertex_id_hash[vertex_hash["rid"]],
|
|
58
|
+
label: vertex_hash["screen_name"],
|
|
59
|
+
community_id: community_hash[vertex_hash["c_id"]].id,
|
|
60
|
+
}
|
|
61
|
+
graph.vertices.build(params)
|
|
62
|
+
if verbose
|
|
63
|
+
puts ".................... #{Time.now.strftime('%Y-%m-%d %H:%M:%S')} #{i}" if (i % 10000) == 0
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
puts "Loading edges ...... #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}" if verbose
|
|
69
|
+
$stdout.flush if verbose
|
|
70
|
+
start_edge_number = (options[:start_edge_number].to_i rescue 1) || 1
|
|
71
|
+
end_edge_number = (options[:end_edge_number].to_i rescue nil)
|
|
72
|
+
end_edge_number = nil if end_edge_number == 0
|
|
73
|
+
if edges_json_str_or_stream
|
|
74
|
+
json = JSON.load(edges_json_str_or_stream)
|
|
75
|
+
json["result"].each_with_index do |edge_hash, i|
|
|
76
|
+
next if i < start_edge_number
|
|
77
|
+
break if end_edge_number && end_edge_number < i
|
|
78
|
+
params = {
|
|
79
|
+
weight: edge_hash["weight"].to_f,
|
|
80
|
+
source: vertex_id_hash[edge_hash["in"]],
|
|
81
|
+
target: vertex_id_hash[edge_hash["out"]],
|
|
82
|
+
}
|
|
83
|
+
if graph.class::RGRAPHUM::Edge.has_field?(:created_at)
|
|
84
|
+
params[:created_at] = Time.at(edge_hash["created_at"].to_i)
|
|
85
|
+
end
|
|
86
|
+
graph.edges.build(params)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
puts "Loaded! #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}" if verbose
|
|
91
|
+
$stdout.flush if verbose
|
|
92
|
+
|
|
93
|
+
graph
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
module Rgraphum::Marshal
|
|
4
|
+
def self.included(base)
|
|
5
|
+
base.__send__ :include, InstanceMethods
|
|
6
|
+
base.__send__ :extend, ClassMethods
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
module InstanceMethods
|
|
10
|
+
def dump_to(path)
|
|
11
|
+
data = Marshal.dump(self)
|
|
12
|
+
File.write(path, data)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module ClassMethods
|
|
17
|
+
def load_from(path)
|
|
18
|
+
data = File.read(path)
|
|
19
|
+
graph = Marshal.load(data)
|
|
20
|
+
unless graph.is_a?(self)
|
|
21
|
+
raise TypeError, "No #{self} instance in: #{path} (#{graph.class})"
|
|
22
|
+
end
|
|
23
|
+
graph
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Rgraphum::Parsers
|
|
6
|
+
class FlareParser
|
|
7
|
+
class << self
|
|
8
|
+
#
|
|
9
|
+
def builder(graph)
|
|
10
|
+
stream = "var flare = { \"#{graph.label}\" : "
|
|
11
|
+
|
|
12
|
+
vertices_hash = {}
|
|
13
|
+
graph.vertices.each do |vertex|
|
|
14
|
+
vertices_hash[vertex.label] = vertex.amount
|
|
15
|
+
end
|
|
16
|
+
stream += vertices_hash.to_json
|
|
17
|
+
stream += "};"
|
|
18
|
+
stream
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Options:
|
|
23
|
+
#
|
|
24
|
+
def initialize(options={})
|
|
25
|
+
default_options = {
|
|
26
|
+
}
|
|
27
|
+
@options = default_options.merge(options)
|
|
28
|
+
builder(@options[:graph]) if @options.key?(:graph)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def builder(graph)
|
|
32
|
+
@stream = self.class.builder(graph)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_s
|
|
36
|
+
unless @stream
|
|
37
|
+
raise ArgumentError, "Didn't build stream with builder(graph)"
|
|
38
|
+
end
|
|
39
|
+
@stream
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require 'builder/xmlmarkup'
|
|
4
|
+
|
|
5
|
+
module Rgraphum::Parsers
|
|
6
|
+
class GephiParser
|
|
7
|
+
class << self
|
|
8
|
+
def builder(graph)
|
|
9
|
+
new.builder(graph)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Options:
|
|
14
|
+
#
|
|
15
|
+
def initialize(options={})
|
|
16
|
+
default_options = {
|
|
17
|
+
}
|
|
18
|
+
@options = default_options.merge(options)
|
|
19
|
+
builder(@options[:graph]) if @options.key?(:graph)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def builder(graph)
|
|
23
|
+
xml = ""
|
|
24
|
+
@xmlobj = Builder::XmlMarkup.new(target: xml , indent: 2)
|
|
25
|
+
set_header
|
|
26
|
+
|
|
27
|
+
options = {
|
|
28
|
+
"xmlns" => "http://www.gexf.net/1.2draft",
|
|
29
|
+
"xmlns:viz" => "http://www.gexf.net/1.1draft/viz",
|
|
30
|
+
"xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
|
|
31
|
+
"xsi:schemaLocation" => "http://www.gexf.net/1.2draft http://www.gexf.net/1.2draft/gexf.xsd",
|
|
32
|
+
"version" => "1.2",
|
|
33
|
+
}
|
|
34
|
+
@xmlobj.gexf(options) do
|
|
35
|
+
@xmlobj.meta(lastmodifieddate: Time.now.strftime("%Y-%m-%d")) do
|
|
36
|
+
@xmlobj.creator("Gephi 0.8")
|
|
37
|
+
end
|
|
38
|
+
opts = {
|
|
39
|
+
defaultedgetype: "directed",
|
|
40
|
+
mode: "dynamic",
|
|
41
|
+
idtype: "string",
|
|
42
|
+
timeformat: "dateTime",
|
|
43
|
+
}
|
|
44
|
+
@xmlobj.graph(opts) do
|
|
45
|
+
vertices_to_xml(graph.vertices)
|
|
46
|
+
edges_to_xml(graph.edges)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
@xml = xml
|
|
51
|
+
@xml
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def to_s
|
|
55
|
+
unless @xml
|
|
56
|
+
raise ArgumentError, "Didn't build xml with builder(graph)"
|
|
57
|
+
end
|
|
58
|
+
@xml
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def vertices_to_xml(vertices)
|
|
64
|
+
# Vertexの登録
|
|
65
|
+
@xmlobj.nodes() do
|
|
66
|
+
vertices.each do |vertex|
|
|
67
|
+
node_to_xml(vertex)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
alias :nodes_to_xml :vertices_to_xml
|
|
72
|
+
|
|
73
|
+
def node_to_xml(vertex)
|
|
74
|
+
new_vertex = vertex.dup
|
|
75
|
+
new_vertex.start = time_format(vertex.start) if vertex.start
|
|
76
|
+
new_vertex.end = time_format(vertex.end) if vertex.end
|
|
77
|
+
|
|
78
|
+
@xmlobj.node(id: vertex.id, label: vertex.label) do
|
|
79
|
+
# if vertex.attvalues and vertex.attvalues !=[]
|
|
80
|
+
# xmlobj.spells(){
|
|
81
|
+
# vertex.attvalues.each do |attvalue|
|
|
82
|
+
# opts = {
|
|
83
|
+
# start: time_format(attvalue.start),
|
|
84
|
+
# endopen: time_format(attvalue.end),
|
|
85
|
+
# }
|
|
86
|
+
# @xmlobj.spell(opts)
|
|
87
|
+
# end
|
|
88
|
+
# }
|
|
89
|
+
# end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
alias :vertex_to_xml :node_to_xml
|
|
93
|
+
|
|
94
|
+
def edges_to_xml(edges)
|
|
95
|
+
# @xmlobj.attributes(class: "edge", mode: "dynamic") do
|
|
96
|
+
# @xmlobj.attribute(id: "weight", title: "Weight", type: "float")
|
|
97
|
+
# end
|
|
98
|
+
@xmlobj.edges() do
|
|
99
|
+
edges.each do |edge|
|
|
100
|
+
edge_to_xml(edge.dup)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def edge_to_xml(edge)
|
|
106
|
+
edge_opts = {
|
|
107
|
+
id: edge.id,
|
|
108
|
+
source: edge.source.id,
|
|
109
|
+
target: edge.target.id,
|
|
110
|
+
label: edge.label,
|
|
111
|
+
weight: (edge.weight || 0.0),
|
|
112
|
+
}
|
|
113
|
+
@xmlobj.edge(edge_opts) do
|
|
114
|
+
if edge.attvalues and !edge.attvalues.empty?
|
|
115
|
+
mlobj.spells() do
|
|
116
|
+
edge.attvalues.each do |attvalue|
|
|
117
|
+
opts = {
|
|
118
|
+
start: time_format(attvalue.start),
|
|
119
|
+
endopen: time_format(attvalue.end),
|
|
120
|
+
}
|
|
121
|
+
xmlobj.spell(opts)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
xmlobj.attvalues() do
|
|
126
|
+
edge.attvalues.each do |attvalue|
|
|
127
|
+
attrs = {
|
|
128
|
+
:for => "weight",
|
|
129
|
+
:value => attvalue.weight.to_f,
|
|
130
|
+
:start => time_format(attvalue.start),
|
|
131
|
+
:end => time_format(attvalue.end),
|
|
132
|
+
}
|
|
133
|
+
xmlobj.attvalue(attrs)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def make_spells_compact_with(field_name, graph, options={})
|
|
141
|
+
graph.divide_by_time(60 * 8)
|
|
142
|
+
|
|
143
|
+
new_vertices = Rgraphum::Vertices.new
|
|
144
|
+
new_vertices.graph = graph
|
|
145
|
+
|
|
146
|
+
graph.vertices.each do |vertex|
|
|
147
|
+
same_vertex = new_vertices.find { |v|
|
|
148
|
+
v.send(field_name) == vertex.send(field_name)
|
|
149
|
+
}
|
|
150
|
+
unless same_vertex
|
|
151
|
+
new_vertex = vertex.dup
|
|
152
|
+
new_vertex.edges = []
|
|
153
|
+
new_vertices << new_vertex
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
new_edges = Rgraphum::Edges.new
|
|
158
|
+
|
|
159
|
+
graph.edges.each do |edge|
|
|
160
|
+
source_label = edge.source.send(field_name)
|
|
161
|
+
target_label = edge.target.send(field_name)
|
|
162
|
+
|
|
163
|
+
edge.source = new_vertices.find { |vertex| vertex.send(field_name) == source_label }
|
|
164
|
+
edge.target = new_vertices.find { |vertex| vertex.send(field_name) == target_label }
|
|
165
|
+
|
|
166
|
+
new_edge = new_edges.find { |e|
|
|
167
|
+
e.source == edge.source and e.source == edge.source
|
|
168
|
+
}
|
|
169
|
+
if new_edge
|
|
170
|
+
new_edge.weight += edge.weight
|
|
171
|
+
else
|
|
172
|
+
new_edges << edge
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
graph.vertices = new_vertices
|
|
177
|
+
graph.edges = new_edges
|
|
178
|
+
graph
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def set_header
|
|
182
|
+
@xmlobj.instruct! :xml, encoding: 'UTF-8'
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def add_spells(vertex_a, vertex_b)
|
|
186
|
+
raise NotImplementedError
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def time_format(t)
|
|
190
|
+
t.strftime("%Y-%m-%dT%H:%M:%S")
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
5
|
+
module Rgraphum::Parsers
|
|
6
|
+
# Graphviz is Graph drowing application.
|
|
7
|
+
# Using dot formatted file, it drow picture.
|
|
8
|
+
# This class make dot formatted file from graph and write file
|
|
9
|
+
# if you want to know details, please see graphviz manual
|
|
10
|
+
class GraphvizParser
|
|
11
|
+
class << self
|
|
12
|
+
# build graphviz file(.dot) content as string
|
|
13
|
+
# @param [Rgraphum::Graph] graph target graph .
|
|
14
|
+
# @param [String] layout type of layout, "dot", "neato", "fdp", "sfdp", "twopi", "circo". please see graphviz manual.
|
|
15
|
+
def builder(graph, layout="dot")
|
|
16
|
+
dot = "digraph #{graph.label || "sample"} { \n"
|
|
17
|
+
dot += " graph [ layout = \"#{layout}\", overlap = false ] \n\n"
|
|
18
|
+
|
|
19
|
+
graph.edges.each do |edge|
|
|
20
|
+
s = edge[:source][:label] || edge[:source][:id]
|
|
21
|
+
t = edge[:target][:label] || edge[:target][:id]
|
|
22
|
+
label = "[label = \"#{edge[:label]}\"]" if edge[:label]
|
|
23
|
+
dot += " \"#{s}\" -> \"#{t}\" #{label}; \n"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
dot += "}"
|
|
27
|
+
|
|
28
|
+
dot
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# save graph as .dot and image files
|
|
32
|
+
#
|
|
33
|
+
# @param [Rgraphum::Graph] graph
|
|
34
|
+
# @param [String] path_prefix Will create path_prefix.{dot,*} files
|
|
35
|
+
# @param [String] type picture type, "jpeg", "jpg", "png" and etc..
|
|
36
|
+
#
|
|
37
|
+
def export(graph, path_prefix, type)
|
|
38
|
+
dot = builder(graph)
|
|
39
|
+
open("#{path_prefix}.dot", "w") do |file|
|
|
40
|
+
file.write(dot)
|
|
41
|
+
end
|
|
42
|
+
cmd = "dot -T#{type} #{path_prefix}.dot -o #{path_prefix}.#{type}"
|
|
43
|
+
o, e, s = Open3.capture3(cmd)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def initialize(options={})
|
|
48
|
+
default_options = {
|
|
49
|
+
layout: "dot",
|
|
50
|
+
}
|
|
51
|
+
@options = default_options.merge(options)
|
|
52
|
+
builder(@options[:graph]) if @options.key?(:graph)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# dot output builder
|
|
56
|
+
# it call only class.method
|
|
57
|
+
# @see GraphvizParser::builder
|
|
58
|
+
def builder(graph)
|
|
59
|
+
@dot = self.class.builder(graph, @options[:layout])
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# save dot file and image
|
|
63
|
+
# @param [String] path_prefix Will create path_prefix.{dot,*} files
|
|
64
|
+
# @param [String] type picture type, "jpeg", "jpg", "png" and etc..
|
|
65
|
+
def export(path_prefix, type=nil)
|
|
66
|
+
graph = @options[:graph]
|
|
67
|
+
builder(graph) unless @dot
|
|
68
|
+
self.class.export(graph, path_prefix, type)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def to_s
|
|
72
|
+
unless @dot
|
|
73
|
+
raise ArgumentError, "Didn't build dot with builder(graph)"
|
|
74
|
+
end
|
|
75
|
+
@dot
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|