graph_matching 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.rubocop.yml +112 -0
- data/.ruby-version +1 -0
- data/.travis.yml +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +205 -0
- data/Rakefile +9 -0
- data/benchmark/mcm_bipartite/complete_bigraphs/benchmark.rb +33 -0
- data/benchmark/mcm_bipartite/complete_bigraphs/compare.gnuplot +19 -0
- data/benchmark/mcm_bipartite/complete_bigraphs/edges_times_vertexes.data +500 -0
- data/benchmark/mcm_bipartite/complete_bigraphs/plot.gnuplot +21 -0
- data/benchmark/mcm_bipartite/complete_bigraphs/plot.png +0 -0
- data/benchmark/mcm_bipartite/complete_bigraphs/time.data +499 -0
- data/benchmark/mcm_general/complete_graphs/benchmark.rb +30 -0
- data/benchmark/mcm_general/complete_graphs/plot.gnuplot +19 -0
- data/benchmark/mcm_general/complete_graphs/plot.png +0 -0
- data/benchmark/mcm_general/complete_graphs/time.data +499 -0
- data/benchmark/mcm_general/complete_graphs/v_cubed.data +500 -0
- data/benchmark/mwm_bipartite/complete_bigraphs/benchmark.rb +43 -0
- data/benchmark/mwm_bipartite/complete_bigraphs/nmN.data +499 -0
- data/benchmark/mwm_bipartite/complete_bigraphs/nmN.xlsx +0 -0
- data/benchmark/mwm_bipartite/complete_bigraphs/plot.gnuplot +22 -0
- data/benchmark/mwm_bipartite/complete_bigraphs/plot.png +0 -0
- data/benchmark/mwm_bipartite/complete_bigraphs/time.data +299 -0
- data/benchmark/mwm_bipartite/misc/calc_d2/benchmark.rb +29 -0
- data/benchmark/mwm_general/complete_graphs/benchmark.rb +32 -0
- data/benchmark/mwm_general/complete_graphs/compare.gnuplot +19 -0
- data/benchmark/mwm_general/complete_graphs/mn_log_n.data +299 -0
- data/benchmark/mwm_general/complete_graphs/mn_log_n.xlsx +0 -0
- data/benchmark/mwm_general/complete_graphs/plot.gnuplot +22 -0
- data/benchmark/mwm_general/complete_graphs/plot.png +0 -0
- data/benchmark/mwm_general/complete_graphs/time.data +299 -0
- data/benchmark/mwm_general/incomplete_graphs/benchmark.rb +39 -0
- data/benchmark/mwm_general/incomplete_graphs/plot.gnuplot +22 -0
- data/benchmark/mwm_general/incomplete_graphs/plot.png +0 -0
- data/benchmark/mwm_general/incomplete_graphs/time_10_pct.data +299 -0
- data/benchmark/mwm_general/incomplete_graphs/time_20_pct.data +299 -0
- data/benchmark/mwm_general/incomplete_graphs/time_30_pct.data +299 -0
- data/graph_matching.gemspec +35 -0
- data/lib/graph_matching.rb +15 -0
- data/lib/graph_matching/algorithm/matching_algorithm.rb +23 -0
- data/lib/graph_matching/algorithm/mcm_bipartite.rb +118 -0
- data/lib/graph_matching/algorithm/mcm_general.rb +289 -0
- data/lib/graph_matching/algorithm/mwm_bipartite.rb +147 -0
- data/lib/graph_matching/algorithm/mwm_general.rb +1086 -0
- data/lib/graph_matching/algorithm/mwmg_delta_assertions.rb +94 -0
- data/lib/graph_matching/assertion.rb +41 -0
- data/lib/graph_matching/core_ext/set.rb +36 -0
- data/lib/graph_matching/directed_edge_set.rb +31 -0
- data/lib/graph_matching/errors.rb +23 -0
- data/lib/graph_matching/graph/bigraph.rb +37 -0
- data/lib/graph_matching/graph/graph.rb +63 -0
- data/lib/graph_matching/graph/weighted.rb +112 -0
- data/lib/graph_matching/graph/weighted_bigraph.rb +17 -0
- data/lib/graph_matching/graph/weighted_graph.rb +17 -0
- data/lib/graph_matching/integer_vertexes.rb +29 -0
- data/lib/graph_matching/matching.rb +120 -0
- data/lib/graph_matching/ordered_set.rb +59 -0
- data/lib/graph_matching/version.rb +6 -0
- data/lib/graph_matching/visualize.rb +93 -0
- data/profile/mcm_bipartite/compare.sh +15 -0
- data/profile/mcm_bipartite/publish.sh +12 -0
- data/profile/mwm_general/compare.sh +15 -0
- data/profile/mwm_general/profile.rb +28 -0
- data/profile/mwm_general/publish.sh +12 -0
- data/research/1965_edmonds.pdf +0 -0
- data/research/1975_even_kariv.pdf +0 -0
- data/research/1976_gabow.pdf +0 -0
- data/research/1980_micali_vazirani.pdf +0 -0
- data/research/1985_gabow.pdf +0 -0
- data/research/2002_tarjan.pdf +0 -0
- data/research/2013_zwick.pdf +0 -0
- data/research/examples/unweighted_general/1.txt +86 -0
- data/research/goodwin.pdf +0 -0
- data/research/kavathekar-scribe.pdf +0 -0
- data/research/kusner.pdf +0 -0
- data/research/van_rantwijk/mwm_example.py +19 -0
- data/research/van_rantwijk/mwmatching.py +945 -0
- data/spec/graph_matching/algorithm/matching_algorithm_spec.rb +14 -0
- data/spec/graph_matching/algorithm/mcm_bipartite_spec.rb +98 -0
- data/spec/graph_matching/algorithm/mcm_general_spec.rb +159 -0
- data/spec/graph_matching/algorithm/mwm_bipartite_spec.rb +82 -0
- data/spec/graph_matching/algorithm/mwm_general_spec.rb +439 -0
- data/spec/graph_matching/graph/bigraph_spec.rb +73 -0
- data/spec/graph_matching/graph/graph_spec.rb +53 -0
- data/spec/graph_matching/graph/weighted_spec.rb +29 -0
- data/spec/graph_matching/integer_vertexes_spec.rb +21 -0
- data/spec/graph_matching/matching_spec.rb +89 -0
- data/spec/graph_matching/visualize_spec.rb +38 -0
- data/spec/graph_matching_spec.rb +9 -0
- data/spec/spec_helper.rb +26 -0
- metadata +263 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
module GraphMatching
|
2
|
+
module Algorithm
|
3
|
+
# Can be mixed into MWMGeneral to add runtime assertions
|
4
|
+
# about the data structures used for delta2/delta3 calculations.
|
5
|
+
#
|
6
|
+
# > Check delta2/delta3 computation after every substage;
|
7
|
+
# > only works on integer weights, slows down the algorithm to O(n^4).
|
8
|
+
# > (Van Rantwijk, mwmatching.py, line 34)
|
9
|
+
#
|
10
|
+
module MWMGDeltaAssertions
|
11
|
+
def calc_delta_with_assertions(*args)
|
12
|
+
# > Verify data structures for delta2/delta3 computation.
|
13
|
+
# > (Van Rantwijk, mwmatching.py, line 739)
|
14
|
+
check_delta2
|
15
|
+
check_delta3
|
16
|
+
calc_delta_without_assertions(*args)
|
17
|
+
end
|
18
|
+
|
19
|
+
# > Check optimized delta2 against a trivial computation.
|
20
|
+
# > (Van Rantwijk, mwmatching.py, line 580)
|
21
|
+
def check_delta2
|
22
|
+
(0 ... @nvertex).each do |v|
|
23
|
+
if @label[@in_blossom[v]] == MWMGeneral::LBL_FREE
|
24
|
+
bd = nil
|
25
|
+
bk = nil
|
26
|
+
@neighb_end[v].each do |p|
|
27
|
+
k = p / 2 # Note: floor division
|
28
|
+
w = @endpoint[p]
|
29
|
+
if @label[@in_blossom[w]] == MWMGeneral::LBL_S
|
30
|
+
d = slack(k)
|
31
|
+
if bk.nil? || d < bd
|
32
|
+
bk = k
|
33
|
+
bd = d
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
option1 = bk.nil? && @best_edge[v].nil?
|
38
|
+
option2 = !@best_edge[v].nil? && bd == slack(@best_edge[v])
|
39
|
+
unless option1 || option2
|
40
|
+
fail "Assertion failed: Free vertex #{v}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# > Check optimized delta3 against a trivial computation.
|
47
|
+
# > (Van Rantwijk, mwmatching.py, line 598)
|
48
|
+
def check_delta3
|
49
|
+
bk = nil
|
50
|
+
bd = nil
|
51
|
+
tbk = nil
|
52
|
+
tbd = nil
|
53
|
+
(0 ... 2 * @nvertex).each do |b|
|
54
|
+
if @blossom_parent[b].nil? && @label[b] == MWMGeneral::LBL_S
|
55
|
+
blossom_leaves(b).each do |v|
|
56
|
+
@neighb_end[v].each do |p|
|
57
|
+
k = p / 2 # Note: floor division
|
58
|
+
w = @endpoint[p]
|
59
|
+
if @in_blossom[w] != b &&
|
60
|
+
@label[@in_blossom[w]] == MWMGeneral::LBL_S
|
61
|
+
d = slack(k)
|
62
|
+
if bk.nil? || d < bd
|
63
|
+
bk = k
|
64
|
+
bd = d
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
unless @best_edge[b].nil?
|
70
|
+
i, j = @edges[@best_edge[b]].to_a
|
71
|
+
unless @in_blossom[i] == b || @in_blossom[j] == b
|
72
|
+
fail 'Assertion failed'
|
73
|
+
end
|
74
|
+
unless @in_blossom[i] != b || @in_blossom[j] != b
|
75
|
+
fail 'Assertion failed'
|
76
|
+
end
|
77
|
+
unless @label[@in_blossom[i]] == MWMGeneral::LBL_S &&
|
78
|
+
@label[@in_blossom[j]] == MWMGeneral::LBL_S
|
79
|
+
fail 'Assertion failed'
|
80
|
+
end
|
81
|
+
if tbk.nil? || slack(@best_edge[b]) < tbd
|
82
|
+
tbk = @best_edge[b]
|
83
|
+
tbd = slack(@best_edge[b])
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
unless bd == tbd
|
89
|
+
fail 'Assertion failed'
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module GraphMatching
|
4
|
+
# Provides expressive methods for common runtime assertions, e.g.
|
5
|
+
#
|
6
|
+
# assert(banana).is_a(Fruit)
|
7
|
+
#
|
8
|
+
class Assertion
|
9
|
+
attr_reader :obj
|
10
|
+
|
11
|
+
def initialize(obj)
|
12
|
+
@obj = obj
|
13
|
+
end
|
14
|
+
|
15
|
+
def eq(other)
|
16
|
+
unless obj == other
|
17
|
+
fail "Expected #{other}, got #{obj}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def gte(other)
|
22
|
+
unless obj >= other
|
23
|
+
fail "Expected #{obj} to be >= #{other}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# rubocop:disable Style/PredicateName
|
28
|
+
def is_a(klass)
|
29
|
+
unless obj.is_a?(klass)
|
30
|
+
fail TypeError, "Expected #{klass}, got #{obj.class}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
# rubocop:enable Style/PredicateName
|
34
|
+
|
35
|
+
def not_nil
|
36
|
+
if obj.nil?
|
37
|
+
fail "Unexpected nil"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
# There are some methods we'd like to use which were not added
|
6
|
+
# until ruby 2.1. Fortunately, they are implemented in ruby,
|
7
|
+
# so we can simply copy them. If we ever drop support for ruby 2.0,
|
8
|
+
# this file can be deleted.
|
9
|
+
|
10
|
+
unless Set.instance_methods.include?(:intersect?)
|
11
|
+
|
12
|
+
# no-doc
|
13
|
+
class Set
|
14
|
+
# Returns true if the set and the given set have at least one
|
15
|
+
# element in common.
|
16
|
+
# http://www.ruby-doc.org/stdlib-2.2.0/libdoc/set/rdoc/Set.html#method-i-intersect-3F
|
17
|
+
def intersect?(set)
|
18
|
+
unless set.is_a?(Set)
|
19
|
+
fail ArgumentError, "value must be a set"
|
20
|
+
end
|
21
|
+
if size < set.size
|
22
|
+
any? { |o| set.include?(o) }
|
23
|
+
else
|
24
|
+
set.any? { |o| include?(o) }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns true if the set and the given set have no element in
|
29
|
+
# common. This method is the opposite of intersect?.
|
30
|
+
# http://www.ruby-doc.org/stdlib-2.2.0/libdoc/set/rdoc/Set.html#method-i-disjoint-3F
|
31
|
+
def disjoint?(set)
|
32
|
+
!intersect?(set)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module GraphMatching
|
4
|
+
# A `DirectedEdgeSet` is simply a set of directed edges in a
|
5
|
+
# graph. Whether the graph is actually directed or not is
|
6
|
+
# irrelevant, we can still discuss directed edges in an undirected
|
7
|
+
# graph.
|
8
|
+
#
|
9
|
+
# The naive implementation would be to use ruby's `Set` and RGL's
|
10
|
+
# `DirectedEdge`. This class is optimized to use a 2D array
|
11
|
+
# instead. The sub-array at index i represents a set (or subset)
|
12
|
+
# of vertexes adjacent to i.
|
13
|
+
#
|
14
|
+
class DirectedEdgeSet
|
15
|
+
def initialize(graph_size)
|
16
|
+
@edges = Array.new(graph_size + 1) { [] }
|
17
|
+
end
|
18
|
+
|
19
|
+
def add(v, w)
|
20
|
+
edges[v] << w
|
21
|
+
end
|
22
|
+
|
23
|
+
def adjacent_vertices(v)
|
24
|
+
edges[v]
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :edges
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module GraphMatching
|
4
|
+
class GraphMatchingError < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
# no-doc
|
8
|
+
class InvalidVertexNumbering < GraphMatchingError
|
9
|
+
def initialize(msg = nil)
|
10
|
+
msg ||= <<-EOS
|
11
|
+
Expected vertexes to be consecutive positive integers \
|
12
|
+
starting with zero
|
13
|
+
EOS
|
14
|
+
super(msg)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class DisconnectedGraph < GraphMatchingError
|
19
|
+
end
|
20
|
+
|
21
|
+
class NotBipartite < GraphMatchingError
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rgl/bipartite'
|
4
|
+
require_relative 'graph'
|
5
|
+
require_relative '../algorithm/mcm_bipartite'
|
6
|
+
|
7
|
+
module GraphMatching
|
8
|
+
module Graph
|
9
|
+
# A bipartite graph (or bigraph) is a graph whose vertices can
|
10
|
+
# be divided into two disjoint sets U and V such that every
|
11
|
+
# edge connects a vertex in U to one in V.
|
12
|
+
class Bigraph < Graph
|
13
|
+
def maximum_cardinality_matching
|
14
|
+
Algorithm::MCMBipartite.new(self).match
|
15
|
+
end
|
16
|
+
|
17
|
+
# `partition` either returns two disjoint (complementary)
|
18
|
+
# proper subsets of vertexes or raises a NotBipartite error.
|
19
|
+
#
|
20
|
+
# An empty graph is partitioned into two empty sets. This
|
21
|
+
# seems natural, but unfortunately is not the behavior of
|
22
|
+
# RGL's new `bipartite_sets` function. So, we have to check
|
23
|
+
# for the empty case, but at least we don't have to implement
|
24
|
+
# the algorithm ourselves anymore!
|
25
|
+
#
|
26
|
+
def partition
|
27
|
+
if empty?
|
28
|
+
[Set.new, Set.new]
|
29
|
+
else
|
30
|
+
arrays = bipartite_sets
|
31
|
+
fail NotBipartite if arrays.nil?
|
32
|
+
[Set.new(arrays[0]), Set.new(arrays[1])]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rgl/adjacency'
|
4
|
+
require 'rgl/connected_components'
|
5
|
+
require 'set'
|
6
|
+
require_relative '../algorithm/mcm_general'
|
7
|
+
require_relative '../ordered_set'
|
8
|
+
|
9
|
+
autoload(:SecureRandom, 'securerandom')
|
10
|
+
|
11
|
+
module GraphMatching
|
12
|
+
module Graph
|
13
|
+
# Base class for all graphs.
|
14
|
+
class Graph < RGL::AdjacencyGraph
|
15
|
+
def self.[](*args)
|
16
|
+
super.tap(&:vertexes_must_be_integers)
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(*args)
|
20
|
+
super
|
21
|
+
vertexes_must_be_integers
|
22
|
+
end
|
23
|
+
|
24
|
+
# `adjacent_vertex_set` is the same as `adjacent_vertices`
|
25
|
+
# except it returns a `Set` instead of an `Array`. This is
|
26
|
+
# an optimization, performing in O(n), whereas passing
|
27
|
+
# `adjacent_vertices` to `Set.new` would be O(2n).
|
28
|
+
def adjacent_vertex_set(v)
|
29
|
+
s = Set.new
|
30
|
+
each_adjacent(v) do |u| s.add(u) end
|
31
|
+
s
|
32
|
+
end
|
33
|
+
|
34
|
+
def connected?
|
35
|
+
count = 0
|
36
|
+
each_connected_component { count += 1 }
|
37
|
+
count == 1
|
38
|
+
end
|
39
|
+
|
40
|
+
def maximum_cardinality_matching
|
41
|
+
Algorithm::MCMGeneral.new(self).match
|
42
|
+
end
|
43
|
+
|
44
|
+
def max_v
|
45
|
+
vertexes.max
|
46
|
+
end
|
47
|
+
|
48
|
+
def print
|
49
|
+
base_filename = SecureRandom.hex(16)
|
50
|
+
Visualize.new(self).png(base_filename)
|
51
|
+
end
|
52
|
+
|
53
|
+
def vertexes
|
54
|
+
to_a
|
55
|
+
end
|
56
|
+
|
57
|
+
def vertexes_must_be_integers
|
58
|
+
return if vertices.none? { |v| !v.is_a?(Integer) }
|
59
|
+
fail ArgumentError, 'All vertexes must be integers'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module GraphMatching
|
4
|
+
module Graph
|
5
|
+
# The `Weighted` module is mixed into undirected graphs to
|
6
|
+
# support edge weights. Directed graphs are not supported.
|
7
|
+
#
|
8
|
+
# Data Structure
|
9
|
+
# --------------
|
10
|
+
#
|
11
|
+
# Weights are stored in a 2D array. The weight of an edge i,j
|
12
|
+
# is stored twice, at `[i][j]` and `[j][i]`.
|
13
|
+
#
|
14
|
+
# Storing the weight twice wastes memory. A symmetrical matrix
|
15
|
+
# can be stored in a 1D array (http://bit.ly/1DMfLM3)
|
16
|
+
# However, translating the 2D coordinates into a 1D index
|
17
|
+
# marginally increases the cost of access, and this is a read-heavy
|
18
|
+
# structure, so maybe the extra memory is an acceptable trade-off.
|
19
|
+
# It's also conceptually simpler, for what that's worth.
|
20
|
+
#
|
21
|
+
# If directed graphs were supported (they are not) this 2D array
|
22
|
+
# would be an obvious choice.
|
23
|
+
#
|
24
|
+
# Algorithms which operate on weighted graphs are tightly
|
25
|
+
# coupled to this data structure due to optimizations.
|
26
|
+
#
|
27
|
+
module Weighted
|
28
|
+
def self.included(base)
|
29
|
+
base.extend ClassMethods
|
30
|
+
base.class_eval do
|
31
|
+
attr_accessor :weight
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# no-doc
|
36
|
+
module ClassMethods
|
37
|
+
# `.[]` is the recommended, convenient constructor for
|
38
|
+
# weighted graphs. Each argument should be an array with
|
39
|
+
# three integers; the first two represent the edge, the
|
40
|
+
# third, the weight.
|
41
|
+
def [](*args)
|
42
|
+
assert_weighted_edges(args)
|
43
|
+
weightless_edges = args.map { |e| e.slice(0..1) }
|
44
|
+
g = super(*weightless_edges.flatten)
|
45
|
+
g.init_weights
|
46
|
+
args.each do |edge|
|
47
|
+
i, j, weight = edge[0] - 1, edge[1] - 1, edge[2]
|
48
|
+
g.weight[i][j] = weight
|
49
|
+
g.weight[j][i] = weight
|
50
|
+
end
|
51
|
+
g
|
52
|
+
end
|
53
|
+
|
54
|
+
# `assert_weighted_edges` asserts that `ary` is an array
|
55
|
+
# whose elements are all arrays of exactly three elements.
|
56
|
+
# (The first two represent the edge, the third, the weight)
|
57
|
+
def assert_weighted_edges(ary)
|
58
|
+
return if ary.is_a?(Array) && ary.all?(&method(:weighted_edge?))
|
59
|
+
fail 'Invalid array of weighted edges'
|
60
|
+
end
|
61
|
+
|
62
|
+
# `weighted_edge?` returns true if `e` is an array whose
|
63
|
+
# first two elements are integers, and whose third element
|
64
|
+
# is a real number.
|
65
|
+
def weighted_edge?(e)
|
66
|
+
e.is_a?(Array) &&
|
67
|
+
e.length == 3 &&
|
68
|
+
e[0, 2].all? { |i| i.is_a?(Integer) } &&
|
69
|
+
e[2].is_a?(Integer) || e[2].is_a?(Float)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def init_weights
|
74
|
+
@weight = Array.new(num_vertices) { |_| Array.new(num_vertices) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def max_w
|
78
|
+
edges.map { |edge| w(edge.to_a) }.max
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns the weight of an edge. Accessing `#weight` is much
|
82
|
+
# faster, so this method should only be used where
|
83
|
+
# clarity outweighs performance.
|
84
|
+
def w(edge)
|
85
|
+
i, j = edge
|
86
|
+
fail ArgumentError, "Invalid edge: #{edge}" if i.nil? || j.nil?
|
87
|
+
fail "Edge not found: #{edge}" unless has_edge?(*edge)
|
88
|
+
init_weights if @weight.nil?
|
89
|
+
@weight[i - 1][j - 1]
|
90
|
+
end
|
91
|
+
|
92
|
+
# `set_w` sets a single weight. It not efficient, and is
|
93
|
+
# only provided for situations where constructing the entire
|
94
|
+
# graph with `.[]` is not convenient.
|
95
|
+
def set_w(edge, weight)
|
96
|
+
if edge[0].nil? || edge[1].nil?
|
97
|
+
fail ArgumentError, "Invalid edge: #{edge}"
|
98
|
+
end
|
99
|
+
unless weight.is_a?(Integer)
|
100
|
+
fail TypeError, "Edge weight must be integer"
|
101
|
+
end
|
102
|
+
init_weights if @weight.nil?
|
103
|
+
i, j = edge[0] - 1, edge[1] - 1
|
104
|
+
fail "Edge not found: #{edge}" unless has_edge?(*edge)
|
105
|
+
@weight[i] ||= []
|
106
|
+
@weight[j] ||= []
|
107
|
+
@weight[i][j] = weight
|
108
|
+
@weight[j][i] = weight
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require_relative 'weighted'
|
4
|
+
require_relative '../algorithm/mwm_bipartite'
|
5
|
+
|
6
|
+
module GraphMatching
|
7
|
+
module Graph
|
8
|
+
# A bigraph whose edges have weights. See `Weighted`.
|
9
|
+
class WeightedBigraph < Bigraph
|
10
|
+
include Weighted
|
11
|
+
|
12
|
+
def maximum_weighted_matching
|
13
|
+
Algorithm::MWMBipartite.new(self).match
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|