graph_matching 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.rubocop.yml +112 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +9 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +205 -0
  9. data/Rakefile +9 -0
  10. data/benchmark/mcm_bipartite/complete_bigraphs/benchmark.rb +33 -0
  11. data/benchmark/mcm_bipartite/complete_bigraphs/compare.gnuplot +19 -0
  12. data/benchmark/mcm_bipartite/complete_bigraphs/edges_times_vertexes.data +500 -0
  13. data/benchmark/mcm_bipartite/complete_bigraphs/plot.gnuplot +21 -0
  14. data/benchmark/mcm_bipartite/complete_bigraphs/plot.png +0 -0
  15. data/benchmark/mcm_bipartite/complete_bigraphs/time.data +499 -0
  16. data/benchmark/mcm_general/complete_graphs/benchmark.rb +30 -0
  17. data/benchmark/mcm_general/complete_graphs/plot.gnuplot +19 -0
  18. data/benchmark/mcm_general/complete_graphs/plot.png +0 -0
  19. data/benchmark/mcm_general/complete_graphs/time.data +499 -0
  20. data/benchmark/mcm_general/complete_graphs/v_cubed.data +500 -0
  21. data/benchmark/mwm_bipartite/complete_bigraphs/benchmark.rb +43 -0
  22. data/benchmark/mwm_bipartite/complete_bigraphs/nmN.data +499 -0
  23. data/benchmark/mwm_bipartite/complete_bigraphs/nmN.xlsx +0 -0
  24. data/benchmark/mwm_bipartite/complete_bigraphs/plot.gnuplot +22 -0
  25. data/benchmark/mwm_bipartite/complete_bigraphs/plot.png +0 -0
  26. data/benchmark/mwm_bipartite/complete_bigraphs/time.data +299 -0
  27. data/benchmark/mwm_bipartite/misc/calc_d2/benchmark.rb +29 -0
  28. data/benchmark/mwm_general/complete_graphs/benchmark.rb +32 -0
  29. data/benchmark/mwm_general/complete_graphs/compare.gnuplot +19 -0
  30. data/benchmark/mwm_general/complete_graphs/mn_log_n.data +299 -0
  31. data/benchmark/mwm_general/complete_graphs/mn_log_n.xlsx +0 -0
  32. data/benchmark/mwm_general/complete_graphs/plot.gnuplot +22 -0
  33. data/benchmark/mwm_general/complete_graphs/plot.png +0 -0
  34. data/benchmark/mwm_general/complete_graphs/time.data +299 -0
  35. data/benchmark/mwm_general/incomplete_graphs/benchmark.rb +39 -0
  36. data/benchmark/mwm_general/incomplete_graphs/plot.gnuplot +22 -0
  37. data/benchmark/mwm_general/incomplete_graphs/plot.png +0 -0
  38. data/benchmark/mwm_general/incomplete_graphs/time_10_pct.data +299 -0
  39. data/benchmark/mwm_general/incomplete_graphs/time_20_pct.data +299 -0
  40. data/benchmark/mwm_general/incomplete_graphs/time_30_pct.data +299 -0
  41. data/graph_matching.gemspec +35 -0
  42. data/lib/graph_matching.rb +15 -0
  43. data/lib/graph_matching/algorithm/matching_algorithm.rb +23 -0
  44. data/lib/graph_matching/algorithm/mcm_bipartite.rb +118 -0
  45. data/lib/graph_matching/algorithm/mcm_general.rb +289 -0
  46. data/lib/graph_matching/algorithm/mwm_bipartite.rb +147 -0
  47. data/lib/graph_matching/algorithm/mwm_general.rb +1086 -0
  48. data/lib/graph_matching/algorithm/mwmg_delta_assertions.rb +94 -0
  49. data/lib/graph_matching/assertion.rb +41 -0
  50. data/lib/graph_matching/core_ext/set.rb +36 -0
  51. data/lib/graph_matching/directed_edge_set.rb +31 -0
  52. data/lib/graph_matching/errors.rb +23 -0
  53. data/lib/graph_matching/graph/bigraph.rb +37 -0
  54. data/lib/graph_matching/graph/graph.rb +63 -0
  55. data/lib/graph_matching/graph/weighted.rb +112 -0
  56. data/lib/graph_matching/graph/weighted_bigraph.rb +17 -0
  57. data/lib/graph_matching/graph/weighted_graph.rb +17 -0
  58. data/lib/graph_matching/integer_vertexes.rb +29 -0
  59. data/lib/graph_matching/matching.rb +120 -0
  60. data/lib/graph_matching/ordered_set.rb +59 -0
  61. data/lib/graph_matching/version.rb +6 -0
  62. data/lib/graph_matching/visualize.rb +93 -0
  63. data/profile/mcm_bipartite/compare.sh +15 -0
  64. data/profile/mcm_bipartite/publish.sh +12 -0
  65. data/profile/mwm_general/compare.sh +15 -0
  66. data/profile/mwm_general/profile.rb +28 -0
  67. data/profile/mwm_general/publish.sh +12 -0
  68. data/research/1965_edmonds.pdf +0 -0
  69. data/research/1975_even_kariv.pdf +0 -0
  70. data/research/1976_gabow.pdf +0 -0
  71. data/research/1980_micali_vazirani.pdf +0 -0
  72. data/research/1985_gabow.pdf +0 -0
  73. data/research/2002_tarjan.pdf +0 -0
  74. data/research/2013_zwick.pdf +0 -0
  75. data/research/examples/unweighted_general/1.txt +86 -0
  76. data/research/goodwin.pdf +0 -0
  77. data/research/kavathekar-scribe.pdf +0 -0
  78. data/research/kusner.pdf +0 -0
  79. data/research/van_rantwijk/mwm_example.py +19 -0
  80. data/research/van_rantwijk/mwmatching.py +945 -0
  81. data/spec/graph_matching/algorithm/matching_algorithm_spec.rb +14 -0
  82. data/spec/graph_matching/algorithm/mcm_bipartite_spec.rb +98 -0
  83. data/spec/graph_matching/algorithm/mcm_general_spec.rb +159 -0
  84. data/spec/graph_matching/algorithm/mwm_bipartite_spec.rb +82 -0
  85. data/spec/graph_matching/algorithm/mwm_general_spec.rb +439 -0
  86. data/spec/graph_matching/graph/bigraph_spec.rb +73 -0
  87. data/spec/graph_matching/graph/graph_spec.rb +53 -0
  88. data/spec/graph_matching/graph/weighted_spec.rb +29 -0
  89. data/spec/graph_matching/integer_vertexes_spec.rb +21 -0
  90. data/spec/graph_matching/matching_spec.rb +89 -0
  91. data/spec/graph_matching/visualize_spec.rb +38 -0
  92. data/spec/graph_matching_spec.rb +9 -0
  93. data/spec/spec_helper.rb +26 -0
  94. metadata +263 -0
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'graph_matching/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'graph_matching'
8
+ spec.version = GraphMatching::VERSION
9
+ spec.authors = ['Jared Beck']
10
+ spec.email = ['jared@jaredbeck.com']
11
+ spec.summary = 'Finds maximum matchings in undirected graphs.'
12
+ spec.description = <<-EOS
13
+ Efficient algorithms for maximum cardinality
14
+ and weighted matchings in undirected graphs.
15
+ EOS
16
+ spec.homepage = 'https://github.com/jaredbeck/graph_matching'
17
+ spec.license = 'MIT'
18
+
19
+ spec.files = `git ls-files -z`.split("\x0")
20
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.required_ruby_version = '>= 1.9.3'
25
+
26
+ spec.add_runtime_dependency 'rgl', '~> 0.5.0'
27
+
28
+ spec.add_development_dependency 'bundler'
29
+ spec.add_development_dependency 'pry'
30
+ spec.add_development_dependency 'pry-nav'
31
+ spec.add_development_dependency 'rspec-core', '~> 3.2'
32
+ spec.add_development_dependency 'rspec-expectations', '~> 3.2'
33
+ spec.add_development_dependency 'rspec-mocks', '~> 3.2'
34
+ spec.add_development_dependency 'rubocop'
35
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ require 'graph_matching/core_ext/set'
4
+ require 'graph_matching/graph/graph'
5
+ require 'graph_matching/graph/bigraph'
6
+ require 'graph_matching/graph/weighted_graph'
7
+ require 'graph_matching/graph/weighted_bigraph'
8
+ require 'graph_matching/errors'
9
+ require 'graph_matching/version'
10
+ require 'graph_matching/visualize'
11
+
12
+ # Efficient algorithms for maximum cardinality and weighted
13
+ # matchings in undirected graphs.
14
+ module GraphMatching
15
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative '../assertion'
4
+
5
+ module GraphMatching
6
+ module Algorithm
7
+
8
+ # All matching algorithms operate on a graph, hence the
9
+ # common constructor.
10
+ class MatchingAlgorithm
11
+ attr_reader :g
12
+
13
+ def initialize(graph)
14
+ @g = graph
15
+ end
16
+
17
+ def assert(obj)
18
+ Assertion.new(obj)
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,118 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative '../matching'
4
+ require_relative 'matching_algorithm'
5
+
6
+ module GraphMatching
7
+ module Algorithm
8
+
9
+ # `MCMBipartite` implements Maximum Cardinality Matching in
10
+ # bipartite graphs.
11
+ class MCMBipartite < MatchingAlgorithm
12
+
13
+ def initialize(graph)
14
+ assert(graph).is_a(Graph::Bigraph)
15
+ super
16
+ end
17
+
18
+ def match
19
+ u, v = g.partition
20
+ m = []
21
+
22
+ while true
23
+
24
+ # Begin each stage by clearing all labels and marks
25
+ t = []
26
+ predecessors = {}
27
+ aug_path = nil
28
+
29
+ # Label unmatched vertexes in U with label R. These R-vertexes
30
+ # are candidates for the start of an augmenting path.
31
+ unmarked = r = u.select { |i| m[i] == nil }
32
+
33
+ # While there are unmarked R-vertexes
34
+ while aug_path.nil? && start = unmarked.pop
35
+
36
+ # Follow the unmatched edges (if any) to vertexes in V
37
+ # ignoring any V-vertexes already labeled T
38
+ unlabeled_across_unmatched_edges_from(start, g, m ,t).each do |vi|
39
+ t << vi
40
+ predecessors[vi] = start
41
+
42
+ # If there are matched edges, follow each to a vertex
43
+ # in U and label the U-vertex with R. Otherwise,
44
+ # backtrack to construct an augmenting path.
45
+ adj_u_in_m = matched_adjacent(vi, start, g, m)
46
+
47
+ adj_u_in_m.each do |ui|
48
+ r << ui
49
+ predecessors[ui] = vi
50
+ end
51
+
52
+ if adj_u_in_m.empty?
53
+ aug_path = backtrack_from(vi, predecessors)
54
+ break
55
+ end
56
+ end
57
+ end
58
+
59
+ if aug_path.nil?
60
+ break
61
+ else
62
+ m = augment(m, aug_path)
63
+ end
64
+ end
65
+
66
+ Matching.gabow(m)
67
+ end
68
+
69
+ private
70
+
71
+ def assert_valid_aug_path(path)
72
+ unless path.length >= 2 && path.length.even?
73
+ raise "Invalid augmenting path"
74
+ end
75
+ end
76
+
77
+ def augment(m, path)
78
+ assert_valid_aug_path(path)
79
+ ix = 0
80
+ while ix < path.length
81
+ i = path[ix]
82
+ j = path[ix + 1]
83
+ mi = m[i]
84
+ mj = m[j]
85
+ m[mi] = nil unless mi == nil
86
+ m[mj] = nil unless mj == nil
87
+ m[i] = j
88
+ m[j] = i
89
+ ix += 2
90
+ end
91
+ m
92
+ end
93
+
94
+ def backtrack_from(end_vertex, predecessors)
95
+ augmenting_path = [end_vertex]
96
+ while predecessors.has_key?(augmenting_path.last)
97
+ augmenting_path.push(predecessors[augmenting_path.last])
98
+ end
99
+ augmenting_path
100
+ end
101
+
102
+ def matched_adjacent(from, except, g, m)
103
+ g.adjacent_vertices(from).select { |i|
104
+ i != except && m[from] == i
105
+ }
106
+ end
107
+
108
+ # Look across unmatched edges from `v` to find vertexes
109
+ # not labeled `t`.
110
+ def unlabeled_across_unmatched_edges_from(v, g, m, t)
111
+ g.adjacent_vertices(v).select { |i|
112
+ m[v] != i && !t.include?(i)
113
+ }
114
+ end
115
+
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,289 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative '../directed_edge_set'
4
+ require_relative '../matching'
5
+ require_relative 'matching_algorithm'
6
+
7
+ module GraphMatching
8
+ module Algorithm
9
+
10
+ # `MCMGeneral` implements Maximum Cardinality Matching in
11
+ # general graphs (as opposed to bipartite).
12
+ class MCMGeneral < MatchingAlgorithm
13
+
14
+ # An LFlag represents a flag on an edge during Gabow's `l` function.
15
+ class LFlag
16
+ attr_reader :edge
17
+ def initialize(edge)
18
+ @edge = edge
19
+ end
20
+ end
21
+
22
+ def initialize(graph)
23
+ assert(graph).is_a(Graph::Graph)
24
+ super
25
+ end
26
+
27
+ def match
28
+ return Matching.new if g.empty?
29
+ raise DisconnectedGraph unless g.connected?
30
+ e(g)
31
+ end
32
+
33
+ private
34
+
35
+ # `e` constructs a maximum matching on a graph. It starts a
36
+ # search for an augmenting path to each unmatched vertex u.
37
+ # It scans edges of the graph, deciding to assign new labels
38
+ # or to augment the matching.
39
+ def e(g)
40
+ first = []
41
+ label = []
42
+ mate = []
43
+
44
+ # E0. [Initialize.] Read the graph into adjacency lists,
45
+ # numbering the vertices 1 to V and the edges V + 1 to
46
+ # V + 2W. Create a dummy vertex 0 For 0 <= i <= V, set
47
+ # LABEL(u) <- -1, MATE(i) <- 0 (all vertices are nonouter
48
+ # and unmatched) Set u <- 0
49
+
50
+ label.fill(-1, 0, g.size + 1)
51
+ mate.fill(0, 0, g.size + 1)
52
+ u = 0
53
+
54
+ # El. [Find unmatched vertex ] Set u = u + 1. If u > V,
55
+ # halt; MATE contains a maximum matching Otherwise, if vertex
56
+ # u is matched, repeat step E1 Otherwise (u is unmatched, so
57
+ # assign a start label and begin a new search)
58
+ # set LABEL(u) = FIRST(u) = 0
59
+
60
+ while true do
61
+ u += 1
62
+ break if u > g.size
63
+ if mate[u] != 0
64
+ next # repeat E1
65
+ else
66
+ label[u] = first[u] = 0
67
+ end
68
+
69
+ # E2 [Choose an edge ] Choose an edge xy, where x is an outer
70
+ # vertex. (An edge vw may be chosen twice in a search--once
71
+ # with x = v, and once with x = w.) If no such edge exists,
72
+ # go to E7. (Edges xy can be chosen in an arbitrary order. A
73
+ # possible choice method is "breadth-first": an outer vertex
74
+ # x = x1 is chosen, and edges (x1,y) are chosen in succeeding
75
+ # executions of E2, when all such edges have been chosen, the
76
+ # vertex x2 that was labeled immediately after x1 is chosen,
77
+ # and the process is repeated for x = x2. This breadth-first
78
+ # method requires that Algorithm E maintain a list of outer
79
+ # vertices, x1, x2, ...)
80
+
81
+ searching = true
82
+ visited_nodes = Set.new
83
+ visited_edges = DirectedEdgeSet.new(g.size)
84
+ q = OrderedSet[u]
85
+ while searching && !q.empty?
86
+ x = q.deq
87
+ visited_nodes.add(x)
88
+ adjacent = g.adjacent_vertex_set(x)
89
+ discovered = adjacent - visited_edges.adjacent_vertices(x)
90
+
91
+ discovered.each do |y|
92
+ visited_edges.add(x, y)
93
+
94
+ # E3. [Augment the matching.] If y is unmatched and y != u,
95
+ # set MATE(y) = x, call R(x, y): then go to E7 (R
96
+ # completes the augment along path (y)*P(x))
97
+
98
+ if mate[y] == 0 && y != u
99
+ mate[y] = x
100
+ r(x, y, label, mate)
101
+ searching = false # go to E7
102
+ break
103
+
104
+ # E4. [Assign edge labels.] If y is outer, call L, then go to
105
+ # E2 (L assigns edge label n(xy) to nonouter vertices in P(x)
106
+ # and P(y))
107
+
108
+ elsif outer?(label[y])
109
+ l(x, y, first, label, mate, q, visited_nodes)
110
+
111
+ # E5. [Assign a vertex label.] Set v <- MATE(y). If v is
112
+ # nonouter, set LABEL(v) <- x, FIRST(v) <- y, and go to E2
113
+ #
114
+ # E6. [Get next edge.] Go to E2 (y is nonouter and MATE(y) is
115
+ # outer, so edge xy adds nothing).
116
+
117
+ else
118
+ v = mate[y]
119
+ if label[v] == -1 # nonouter
120
+ label[v] = x
121
+ first[v] = y
122
+ end
123
+ unless visited_nodes.include?(v)
124
+ q.enq(v)
125
+ end
126
+ end
127
+
128
+ end
129
+ end
130
+
131
+ #
132
+ # E7. [Stop the search] Set LABEL(O) <- -1. For all outer
133
+ # vertices i set LABEL(i) <- LABEL(MATE(i)) <- -1 Then go
134
+ # to E1 (now all vertexes are nonouter for the next search).
135
+ #
136
+
137
+ label[0] = -1
138
+ label.each_with_index do |obj, ix|
139
+ if ix > 0 && outer?(obj)
140
+ label[ix] = label[mate[ix]] = -1
141
+ end
142
+ end
143
+
144
+ end # while e0_loop
145
+
146
+ Matching.gabow(mate)
147
+ end
148
+
149
+ def edge_label?(label_value)
150
+ label_value.is_a?(RGL::Edge::UnDirectedEdge)
151
+ end
152
+
153
+ # L assigns the edge label n(xy) to nonouter vertices. Edge xy
154
+ # joins outer vertices x, y. L sets join to the first nonouter
155
+ # vertex in both P(x) and P(y). Then it labels all nonouter
156
+ # vertices preceding join in P(x) or P(y).
157
+ def l(x, y, first, label, mate, q, visited_nodes)
158
+
159
+ # L0. [Initialize.] Set r <- FIRST(x), s <= FIRST(y).
160
+ # If r = s, return (no vertices can be labeled).
161
+ # Otherwise flag r and s. (Steps L1-L2 find join by advancing
162
+ # alternately along paths P(x) and P(y). Flags are assigned
163
+ # to nonouter vertices r in these paths. This is done by
164
+ # setting LABEL(r) to a negative edge number, LABEL(r) <- -n(xy).
165
+ # This way, each invocation of L uses a distinct flag value.)
166
+
167
+ r = first[x]
168
+ s = first[y]
169
+
170
+ if r == s
171
+ return # no vertices can be labeled
172
+ else
173
+ label[r] = LFlag.new(n(x, y))
174
+ end
175
+
176
+ # L1. [Switch paths ] If s != 0, interchange r and s, r <-> s
177
+ # (r is a flagged nonouter vertex, alternately in P(x) and P(y)).
178
+
179
+ finding_join = true
180
+ while finding_join
181
+ if s != 0
182
+ temp = r
183
+ r = s
184
+ s = temp
185
+ end
186
+
187
+ # L2. [Next nonouter vertex.] Set r <- FIRST(LABEL(MATE(r)))
188
+ # (r is set to the next nonouter vertex in P(x) or P(y)). If
189
+ # r is not flagged, flag r and go to L1 Otherwise set
190
+ # join <- r and go to L3.
191
+
192
+ r = first[label[mate[r]]]
193
+ if label[r].is_a?(LFlag)
194
+ join = r
195
+ finding_join = false
196
+ else
197
+ label[r] = LFlag.new(n(x, y))
198
+ end
199
+ end
200
+
201
+ # L3. [Label vertices in P(x), P(y).] (All nonouter vertexes
202
+ # between x and join, or y and join, will be assigned edge
203
+ # labels. See Figure 4(a).) Set v <- FIRST(x) and do L4. Then
204
+ # set v <- FIRST(y) and do L4. Then go to L5.
205
+
206
+ [first[x], first[y]].each do |v|
207
+
208
+ # L4 [Label v] If v != join, set LABEL(v) <- n(xy), FIRST(v) <- join,
209
+ # v <- FIRST(LABEL(MATE(v))) and repeat step L4
210
+ # Otherwise continue as specified in L3.
211
+
212
+ until v == join
213
+ label[v] = n(x, y)
214
+ unless visited_nodes.include?(v)
215
+ q.enq(v)
216
+ end
217
+ first[v] = join
218
+ v = first[label[mate[v]]]
219
+ end
220
+ end
221
+
222
+ # L5 [Update FIRST] For each outer vertex i, if FIRST(i) is
223
+ # outer, set FIRST(i) <- join. (Join is now the first nonouter
224
+ # vertex in P(i))
225
+
226
+ label.each_with_index do |l, i|
227
+ if i > 0 && outer?(l) && outer?(label[first[i]])
228
+ first[i] = join
229
+ end
230
+ end
231
+
232
+ # L6. [Done] Return
233
+ end
234
+
235
+ # Gabow (1976) describes a function `n` which returns the number
236
+ # of the edge from `x` to `y`. Because we are using RGL, and
237
+ # not implementing our own adjacency lists, we can simply return
238
+ # an RGL::Edge::UnDirectedEdge.
239
+ def n(x, y)
240
+ RGL::Edge::UnDirectedEdge.new(x, y)
241
+ end
242
+
243
+ def outer?(label_value)
244
+ !label_value.is_a?(Integer) || label_value >= 0
245
+ end
246
+
247
+ # R(v, w) rematches edges in the augmenting path. Vertex v is
248
+ # outer. Part of path (w) * P(v) is in the augmenting path. It
249
+ # gets re-matched by R(v, w) (Although R sets MATE(v) <- w, it
250
+ # does not set MATE(w) <- v. This is done in step E3 or another
251
+ # call to R.) R is a recursive routine.
252
+ def r(v, w, label, mate)
253
+
254
+ # R1. [Match v to w ] Set t <- MATE(v), MATE(v) <- w.
255
+ # If MATE(t) != v, return (the path is completely re-matched)
256
+
257
+ t = mate[v]
258
+ mate[v] = w
259
+ return if mate[t] != v
260
+
261
+ # R2. [Rematch a path.] If v has a vertex label, set
262
+ # MATE(t) <- LABEL(v), call R(LABEL(v), t) recursively, and
263
+ # then return.
264
+
265
+ if vertex_label?(label[v])
266
+ mate[t] = label[v]
267
+ r(label[v], t, label, mate)
268
+
269
+ # R3. [Rematch two paths.] (Vertex v has an edge label)
270
+ # Set x, y to vertices so LABEL(v) = n(xy), call R(x, y)
271
+ # recursively, call R(y, x) recursively, and then return.
272
+
273
+ elsif edge_label?(label[v])
274
+ x, y = label[v].to_a
275
+ r(x, y, label, mate)
276
+ r(y, x, label, mate)
277
+ else
278
+ fail "Vertex #{v} has an unexpected label type"
279
+ end
280
+ end
281
+
282
+ def vertex_label?(label_value)
283
+ label_value.is_a?(Integer) && label_value > 0
284
+ end
285
+
286
+ end
287
+
288
+ end
289
+ end