plexus 0.5.4 → 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/Gemfile +3 -0
  2. data/LICENSE +37 -0
  3. data/README.md +208 -0
  4. data/Rakefile +25 -0
  5. data/lib/plexus.rb +90 -0
  6. data/lib/plexus/adjacency_graph.rb +225 -0
  7. data/lib/plexus/arc.rb +60 -0
  8. data/lib/plexus/arc_number.rb +50 -0
  9. data/lib/plexus/biconnected.rb +84 -0
  10. data/lib/plexus/chinese_postman.rb +91 -0
  11. data/lib/plexus/classes/graph_classes.rb +28 -0
  12. data/lib/plexus/common.rb +63 -0
  13. data/lib/plexus/comparability.rb +63 -0
  14. data/lib/plexus/directed_graph.rb +78 -0
  15. data/lib/plexus/directed_graph/algorithms.rb +95 -0
  16. data/lib/plexus/directed_graph/distance.rb +167 -0
  17. data/lib/plexus/dot.rb +94 -0
  18. data/lib/plexus/edge.rb +38 -0
  19. data/lib/plexus/ext.rb +79 -0
  20. data/lib/plexus/graph.rb +628 -0
  21. data/lib/plexus/graph_api.rb +35 -0
  22. data/lib/plexus/labels.rb +112 -0
  23. data/lib/plexus/maximum_flow.rb +77 -0
  24. data/lib/plexus/ruby_compatibility.rb +17 -0
  25. data/lib/plexus/search.rb +510 -0
  26. data/lib/plexus/strong_components.rb +93 -0
  27. data/lib/plexus/support/support.rb +9 -0
  28. data/lib/plexus/undirected_graph.rb +56 -0
  29. data/lib/plexus/undirected_graph/algorithms.rb +90 -0
  30. data/lib/plexus/version.rb +6 -0
  31. data/spec/biconnected_spec.rb +27 -0
  32. data/spec/chinese_postman_spec.rb +27 -0
  33. data/spec/community_spec.rb +44 -0
  34. data/spec/complement_spec.rb +27 -0
  35. data/spec/digraph_distance_spec.rb +121 -0
  36. data/spec/digraph_spec.rb +339 -0
  37. data/spec/dot_spec.rb +48 -0
  38. data/spec/edge_spec.rb +158 -0
  39. data/spec/inspection_spec.rb +38 -0
  40. data/spec/multi_edge_spec.rb +32 -0
  41. data/spec/neighborhood_spec.rb +36 -0
  42. data/spec/properties_spec.rb +146 -0
  43. data/spec/search_spec.rb +227 -0
  44. data/spec/spec.opts +4 -0
  45. data/spec/spec_helper.rb +59 -0
  46. data/spec/strong_components_spec.rb +61 -0
  47. data/spec/triangulated_spec.rb +125 -0
  48. data/spec/undirected_graph_spec.rb +220 -0
  49. data/vendor/priority-queue/CHANGELOG +33 -0
  50. data/vendor/priority-queue/Makefile +140 -0
  51. data/vendor/priority-queue/README +133 -0
  52. data/vendor/priority-queue/benchmark/dijkstra.rb +171 -0
  53. data/vendor/priority-queue/compare_comments.rb +49 -0
  54. data/vendor/priority-queue/doc/c-vs-rb.png +0 -0
  55. data/vendor/priority-queue/doc/compare_big.gp +14 -0
  56. data/vendor/priority-queue/doc/compare_big.png +0 -0
  57. data/vendor/priority-queue/doc/compare_small.gp +15 -0
  58. data/vendor/priority-queue/doc/compare_small.png +0 -0
  59. data/vendor/priority-queue/doc/results.csv +37 -0
  60. data/vendor/priority-queue/ext/priority_queue/CPriorityQueue/extconf.rb +2 -0
  61. data/vendor/priority-queue/ext/priority_queue/CPriorityQueue/priority_queue.c +947 -0
  62. data/vendor/priority-queue/lib/priority_queue.rb +14 -0
  63. data/vendor/priority-queue/lib/priority_queue/c_priority_queue.rb +1 -0
  64. data/vendor/priority-queue/lib/priority_queue/poor_priority_queue.rb +46 -0
  65. data/vendor/priority-queue/lib/priority_queue/ruby_priority_queue.rb +526 -0
  66. data/vendor/priority-queue/priority_queue.so +0 -0
  67. data/vendor/priority-queue/setup.rb +1551 -0
  68. data/vendor/priority-queue/test/priority_queue_test.rb +371 -0
  69. data/vendor/rdot.rb +360 -0
  70. metadata +100 -10
@@ -0,0 +1,60 @@
1
+ module Plexus
2
+ # Arc includes classes for representing egdes of directed and
3
+ # undirected graphs. There is no need for a Vertex class, because any ruby
4
+ # object can be a vertex of a graph.
5
+ #
6
+ # Arc's base is a Struct with a :source, a :target and a :label.
7
+ Struct.new("ArcBase", :source, :target, :label)
8
+
9
+ class Arc < Struct::ArcBase
10
+ def initialize(p_source, p_target, p_label = nil)
11
+ super(p_source, p_target, p_label)
12
+ end
13
+
14
+ # Ignore labels for equality.
15
+ def eql?(other)
16
+ self.class == other.class and target == other.target and source == other.source
17
+ end
18
+ alias == eql?
19
+
20
+ # Returns (v,u) if self == (u,v).
21
+ def reverse()
22
+ self.class.new(target, source, label)
23
+ end
24
+
25
+ # Sort support.
26
+ def <=>(rhs)
27
+ [source, target] <=> [rhs.source, rhs.target]
28
+ end
29
+
30
+ # Arc[1,2].to_s => "(1-2)"
31
+ # Arc[1,2,'test'].to_s => "(1-2 test)"
32
+ def to_s
33
+ l = label ? " '#{label.to_s}'" : ''
34
+ "(#{source}-#{target}#{l})"
35
+ end
36
+
37
+ # Hash is defined in such a way that label is not
38
+ # part of the hash value
39
+ # FIXME: I had to get rid of that in order to make to_dot_graph
40
+ # work, but I can't figure it out (doesn't show up in the stack!)
41
+ def hash
42
+ source.hash ^ (target.hash + 1)
43
+ end
44
+
45
+ # Shortcut constructor.
46
+ #
47
+ # Instead of Arc.new(1,2) one can use Arc[1,2].
48
+ def self.[](p_source, p_target, p_label = nil)
49
+ new(p_source, p_target, p_label)
50
+ end
51
+
52
+ def inspect
53
+ "#{self.class.to_s}[#{source.inspect},#{target.inspect},#{label.inspect}]"
54
+ end
55
+ end
56
+
57
+ class MultiArc < Arc
58
+ include ArcNumber
59
+ end
60
+ end
@@ -0,0 +1,50 @@
1
+ module Plexus
2
+ # This module handles internal numbering of edges in order to differente between mutliple edges.
3
+ module ArcNumber
4
+ # Used to differentiate between mutli-edges
5
+ attr_accessor :number
6
+
7
+ def initialize(p_source, p_target, p_number, p_label = nil)
8
+ self.number = p_number
9
+ super(p_source, p_target, p_label)
10
+ end
11
+
12
+ # Returns (v,u) if self == (u,v).
13
+ def reverse
14
+ self.class.new(target, source, number, label)
15
+ end
16
+
17
+ # Allow for hashing of self loops.
18
+ def hash
19
+ super ^ number.hash
20
+ end
21
+
22
+ def to_s
23
+ super + "[#{number}]"
24
+ end
25
+
26
+ def <=>(rhs)
27
+ (result = super(rhs)) == 0 ? number <=> rhs.number : result
28
+ end
29
+
30
+ def inspect
31
+ "#{self.class.to_s}[#{source.inspect},#{target.inspect},#{number.inspect},#{label.inspect}]"
32
+ end
33
+
34
+ def eql?(rhs)
35
+ super(rhs) and (rhs.number.nil? or number.nil? or number == rhs.number)
36
+ end
37
+
38
+ def ==(rhs)
39
+ eql?(rhs)
40
+ end
41
+
42
+ # Shortcut constructor. Instead of Arc.new(1,2) one can use Arc[1,2]
43
+ def self.included(cl)
44
+ # FIXME: lacks a cl.class_eval, no?
45
+ def cl.[](p_source, p_target, p_number = nil, p_label = nil)
46
+ new(p_source, p_target, p_number, p_label)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,84 @@
1
+ module Plexus
2
+
3
+ # Biconnected is a module for adding the biconnected algorithm to
4
+ # UndirectedGraphs
5
+ module Biconnected
6
+
7
+ # biconnected computes the biconnected subgraphs
8
+ # of a graph using Tarjan's algorithm based on DFS. See: Robert E. Tarjan
9
+ # _Depth_First_Search_and_Linear_Graph_Algorithms_. SIAM Journal on
10
+ # Computing, 1(2):146-160, 1972
11
+ #
12
+ # The output of the algorithm is a pair, the first value is an
13
+ # array of biconnected subgraphs. The second is the set of
14
+ # articulation vertices.
15
+ #
16
+ # A connected graph is biconnected if the removal of any single vertex
17
+ # (and all edges incident on that vertex) cannot disconnect the graph.
18
+ # More generally, the biconnected components of a graph are the maximal
19
+ # subsets of vertices such that the removal of a vertex from a particular
20
+ # component will not disconnect the component. Unlike connected components,
21
+ # vertices may belong to multiple biconnected components: those vertices
22
+ # that belong to more than one biconnected component are called articulation
23
+ # points or, equivalently, cut vertices. Articulation points are vertices
24
+ # whose removal would increase the number of connected components in the graph.
25
+ # Thus, a graph without articulation points is biconnected.
26
+ def biconnected
27
+ dfs_num = 0
28
+ number = {}; predecessor = {}; low_point = {}
29
+ stack = []; result = []; articulation= []
30
+
31
+ root_vertex = Proc.new {|v| predecessor[v]=v }
32
+ enter_vertex = Proc.new {|u| number[u]=low_point[u]=(dfs_num+=1) }
33
+ tree_edge = Proc.new do |e|
34
+ stack.push(e)
35
+ predecessor[e.target] = e.source
36
+ end
37
+ back_edge = Proc.new do |e|
38
+ if e.target != predecessor[e.source]
39
+ stack.push(e)
40
+ low_point[e.source] = [low_point[e.source], number[e.target]].min
41
+ end
42
+ end
43
+ exit_vertex = Proc.new do |u|
44
+ parent = predecessor[u]
45
+ is_articulation_point = false
46
+ if number[parent] > number[u]
47
+ parent = predecessor[parent]
48
+ is_articulation_point = true
49
+ end
50
+ if parent == u
51
+ is_articulation_point = false if (number[u] + 1) == number[predecessor[u]]
52
+ else
53
+ low_point[parent] = [low_point[parent], low_point[u]].min
54
+ if low_point[u] >= number[parent]
55
+ if number[parent] > number[predecessor[parent]]
56
+ predecessor[u] = predecessor[parent]
57
+ predecessor[parent] = u
58
+ end
59
+ result << (component = self.class.new)
60
+ while number[stack[-1].source] >= number[u]
61
+ component.add_edge!(stack.pop)
62
+ end
63
+ component.add_edge!(stack.pop)
64
+ if stack.empty?
65
+ predecessor[u] = parent
66
+ predecessor[parent] = u
67
+ end
68
+ end
69
+ end
70
+ articulation << u if is_articulation_point
71
+ end
72
+
73
+ # Execute depth first search
74
+ dfs({:root_vertex => root_vertex,
75
+ :enter_vertex => enter_vertex,
76
+ :tree_edge => tree_edge,
77
+ :back_edge => back_edge,
78
+ :exit_vertex => exit_vertex})
79
+
80
+ [result, articulation]
81
+ end # biconnected
82
+
83
+ end # Biconnected
84
+ end # Plexus
@@ -0,0 +1,91 @@
1
+ module Plexus
2
+ module ChinesePostman
3
+
4
+ # Returns the shortest walk that traverses all arcs at least
5
+ # once, returning to the specified start node.
6
+ def closed_chinese_postman_tour(start, weight=nil, zero=0)
7
+ cost, path, delta = floyd_warshall(weight, zero)
8
+ return nil unless cp_valid_least_cost? cost, zero
9
+ positive, negative = cp_unbalanced(delta)
10
+ f = cp_find_feasible(delta, positive, negative, zero)
11
+ while cp_improve(f, positive, negative, cost, zero); end
12
+ cp_euler_circuit(start, f, path)
13
+ end
14
+
15
+ private
16
+
17
+ def cp_euler_circuit(start, f, path) # :nodoc:
18
+ circuit = [u=v=start]
19
+ bridge_taken = Hash.new {|h,k| h[k] = Hash.new}
20
+ until v.nil?
21
+ if v=f[u].keys.detect {|k| f[u][k] > 0}
22
+ f[u][v] -= 1
23
+ circuit << (u = path[u][v]) while u != v
24
+ else
25
+ unless bridge_taken[u][bridge = path[u][start]]
26
+ v = vertices.detect {|v1| v1 != bridge && edge?(u,v1) && !bridge_taken[u][v1]} || bridge
27
+ bridge_taken[u][v] = true
28
+ circuit << v
29
+ end
30
+ end
31
+ u=v
32
+ end; circuit
33
+ end
34
+
35
+ def cp_cancel_cycle(cost, path, f, start, zero) # :nodoc:
36
+ u = start; k = nil
37
+ begin
38
+ v = path[u][start]
39
+ k = f[v][u] if cost[u][v] < zero and (k.nil? || k > f[v][u])
40
+ end until (u=v) != start
41
+ u = start
42
+ begin
43
+ v = path[u][start]
44
+ cost[u][v] < zero ? f[v][u] -= k : f[u][v] += k
45
+ end until (u=v) != start
46
+ true # This routine always returns true to make cp_improve easier
47
+ end
48
+
49
+ def cp_improve(f, positive, negative, cost, zero) # :nodoc:
50
+ residual = self.class.new
51
+ negative.each do |u|
52
+ positive.each do |v|
53
+ residual.add_edge!(u,v,cost[u][v])
54
+ residual.add_edge!(v,u,-cost[u][v]) if f[u][v] != 0
55
+ end
56
+ end
57
+ r_cost, r_path, r_delta = residual.floyd_warshall(nil, zero)
58
+ i = residual.vertices.detect {|v| r_cost[v][v] and r_cost[v][v] < zero}
59
+ i ? cp_cancel_cycle(r_cost, r_path, f, i) : false
60
+ end
61
+
62
+ def cp_find_feasible(delta, positive, negative, zero) # :nodoc:
63
+ f = Hash.new {|h,k| h[k] = Hash.new}
64
+ negative.each do |i|
65
+ positive.each do |j|
66
+ f[i][j] = -delta[i] < delta[j] ? -delta[i] : delta[j]
67
+ delta[i] += f[i][j]
68
+ delta[j] -= f[i][j]
69
+ end
70
+ end; f
71
+ end
72
+
73
+ def cp_valid_least_cost?(c, zero) # :nodoc:
74
+ vertices.each do |i|
75
+ vertices.each do |j|
76
+ return false unless c[i][j] and c[i][j] >= zero
77
+ end
78
+ end; true
79
+ end
80
+
81
+ def cp_unbalanced(delta) # :nodoc:
82
+ negative = []; positive = []
83
+ vertices.each do |v|
84
+ negative << v if delta[v] < 0
85
+ positive << v if delta[v] > 0
86
+ end; [positive, negative]
87
+ end
88
+
89
+ end # Chinese Postman
90
+ end # Plexus
91
+
@@ -0,0 +1,28 @@
1
+ module Plexus
2
+ # A generic {GraphBuilder Graph} class you can inherit from.
3
+ class Graph; include GraphBuilder; end
4
+
5
+ # A generic {AdjacencyGraphBuilder AdjacencyGraph} class you can inherit from.
6
+ class AdjacencyGraph < Graph; include AdjacencyGraphBuilder; end
7
+
8
+ # A generic {DirectedGraphBuilder DirectedGraph} class you can inherit from.
9
+ class DirectedGraph < Graph; include DirectedGraphBuilder; end
10
+
11
+ # A generic {DigraphBuilder Digraph} class you can inherit from.
12
+ class Digraph < Graph; include DigraphBuilder; end
13
+
14
+ # A generic {DirectedPseudoGraphBuilder DirectedPseudoGraph} class you can inherit from.
15
+ class DirectedPseudoGraph < Graph; include DirectedPseudoGraphBuilder; end
16
+
17
+ # A generic {DirectedMultiGraphBuilder DirectedMultiGraph} class you can inherit from.
18
+ class DirectedMultiGraph < Graph; include DirectedMultiGraphBuilder; end
19
+
20
+ # A generic {UndirectedGraphBuilder UndirectedGraph} class you can inherit from.
21
+ class UndirectedGraph < Graph; include UndirectedGraphBuilder; end
22
+
23
+ # A generic {UndirectedPseudoGraphBuilder UndirectedPseudoGraph} class you can inherit from.
24
+ class UndirectedPseudoGraph < Graph; include UndirectedPseudoGraphBuilder; end
25
+
26
+ # A generic {UndirectedMultiGraphBuilder UndirectedMultiGraph} class you can inherit from.
27
+ class UndirectedMultiGraph < Graph; include UndirectedMultiGraphBuilder; end
28
+ end
@@ -0,0 +1,63 @@
1
+ module Plexus
2
+
3
+ # This class defines a cycle graph of size n.
4
+ # This is easily done by using the base Graph
5
+ # class and implemeting the minimum methods needed to
6
+ # make it work. This is a good example to look
7
+ # at for making one's own graph classes.
8
+ module CycleBuilder
9
+ def initialize(n)
10
+ @size = n;
11
+ end
12
+
13
+ def directed?
14
+ false
15
+ end
16
+
17
+ def vertices
18
+ (1..@size).to_a
19
+ end
20
+
21
+ def vertex?(v)
22
+ v > 0 and v <= @size
23
+ end
24
+
25
+ def edge?(u,v = nil)
26
+ u, v = [u.source, v.target] if u.is_a? Plexus::Arc
27
+ vertex?(u) && vertex?(v) && ((v-u == 1) or (u == @size && v = 1))
28
+ end
29
+
30
+ def edges
31
+ Array.new(@size) { |i| Plexus::Edge[i+1, (i+1) == @size ? 1 : i+2]}
32
+ end
33
+ end # CycleBuilder
34
+
35
+ # This class defines a complete graph of size n.
36
+ # This is easily done by using the base Graph
37
+ # class and implemeting the minimum methods needed to
38
+ # make it work. This is a good example to look
39
+ # at for making one's own graph classes.
40
+ module CompleteBuilder
41
+ include CycleBuilder
42
+
43
+ def initialize(n)
44
+ @size = n
45
+ @edges = nil
46
+ end
47
+
48
+ def edges
49
+ return @edges if @edges # cache edges
50
+ @edges = []
51
+ @size.times do |u|
52
+ @size.times { |v| @edges << Plexus::Edge[u+1, v+1]}
53
+ end
54
+ @edges
55
+ end
56
+
57
+ def edge?(u, v = nil)
58
+ u, v = [u.source, v.target] if u.kind_of? Plexus::Arc
59
+ vertex?(u) && vertex?(v)
60
+ end
61
+ end # CompleteBuilder
62
+
63
+ end # Plexus
@@ -0,0 +1,63 @@
1
+ module Plexus
2
+ module Comparability
3
+
4
+ # A comparability graph is an UndirectedGraph that has a transitive
5
+ # orientation. This returns a boolean that says if this graph
6
+ # is a comparability graph.
7
+ def comparability?() gamma_decomposition[1]; end
8
+
9
+ # Returns an array with two values, the first being a hash of edges
10
+ # with a number containing their class assignment, the second valud
11
+ # is a boolean which states whether or not the graph is a
12
+ # comparability graph
13
+ #
14
+ # Complexity in time O(d*|E|) where d is the maximum degree of a vertex
15
+ # Complexity in space O(|V|+|E|)
16
+ def gamma_decomposition
17
+ k = 0; comparability=true; classification={}
18
+ edges.map {|edge| [edge.source,edge.target]}.each do |e|
19
+ if classification[e].nil?
20
+ k += 1
21
+ classification[e] = k; classification[e.reverse] = -k
22
+ comparability &&= plexus_comparability_explore(e, k, classification)
23
+ end
24
+ end; [classification, comparability]
25
+ end
26
+
27
+ # Returns one of the possible transitive orientations of
28
+ # the UndirectedGraph as a Digraph
29
+ def transitive_orientation(digraph_class=Digraph)
30
+ raise NotImplementError
31
+ end
32
+
33
+ private
34
+
35
+ # Taken from Figure 5.10, on pg. 130 of Martin Golumbic's, _Algorithmic_Graph_
36
+ # _Theory_and_Perfect_Graphs.
37
+ def plexus_comparability_explore(edge, k, classification, space='')
38
+ ret = plexus_comparability_explore_inner(edge, k, classification, :forward, space)
39
+ plexus_comparability_explore_inner(edge.reverse, k, classification, :backward, space) && ret
40
+ end
41
+
42
+ def plexus_comparability_explore_inner(edge, k, classification, direction,space)
43
+ comparability = true
44
+ adj_target = adjacent(edge[1])
45
+ adjacent(edge[0]).select do |mt|
46
+ (classification[[edge[1],mt]] || k).abs < k or
47
+ not adj_target.any? {|adj_t| adj_t == mt}
48
+ end.each do |m|
49
+ e = (direction == :forward) ? [edge[0], m] : [m,edge[0]]
50
+ if classification[e].nil?
51
+ classification[e] = k
52
+ classification[e.reverse] = -k
53
+ comparability = plexus_comparability_explore(e, k, classification, ' '+space) && comparability
54
+ elsif classification[e] == -k
55
+ classification[e] = k
56
+ plexus_comparability_explore(e, k, classification, ' '+space)
57
+ comparability = false
58
+ end
59
+ end; comparability
60
+ end # plexus_comparability_explore_inner
61
+
62
+ end # Comparability
63
+ end # Plexus
@@ -0,0 +1,78 @@
1
+ module Plexus
2
+
3
+ # This implements a directed graph which does not allow parallel
4
+ # edges nor loops. That is, only one arc per nodes couple,
5
+ # and only one parent per node. Mimics the typical hierarchy
6
+ # structure.
7
+ module DirectedGraphBuilder
8
+ include GraphBuilder
9
+
10
+ autoload :Algorithms, "plexus/directed_graph/algorithms"
11
+ autoload :Distance, "plexus/directed_graph/distance"
12
+
13
+ # FIXME: DRY this snippet, I didn't find a clever way to
14
+ # to dit though
15
+ # TODO: well, extends_host_with do ... end would be cool,
16
+ # using Module.new.module_eval(&block) in the helper.
17
+ extends_host
18
+
19
+ module ClassMethods
20
+ def [](*a)
21
+ self.new.from_array(*a)
22
+ end
23
+ end
24
+
25
+ def initialize(*params)
26
+ # FIXME/TODO: setting args to the hash or {} while getting rid
27
+ # on the previous parameters prevents from passing another
28
+ # graph to the initializer, so you cannot do things like:
29
+ # UndirectedGraph.new(Digraph[1,2, 2,3, 2,4, 4,5, 6,4, 1,6])
30
+ # As args must be a hash, if we're to allow such syntax,
31
+ # we should provide a way to handle the graph as a hash
32
+ # member.
33
+ args = (params.pop if params.last.kind_of? Hash) || {}
34
+ args[:algorithmic_category] = DirectedGraphBuilder::Algorithms
35
+ super *(params << args)
36
+ end
37
+ end
38
+
39
+ # DirectedGraph is just an alias for Digraph should one desire
40
+ DigraphBuilder = DirectedGraphBuilder
41
+
42
+ # This is a Digraph that allows for parallel edges, but does not
43
+ # allow loops.
44
+ module DirectedPseudoGraphBuilder
45
+ include DirectedGraphBuilder
46
+ extends_host
47
+
48
+ module ClassMethods
49
+ def [](*a)
50
+ self.new.from_array(*a)
51
+ end
52
+ end
53
+
54
+ def initialize(*params)
55
+ args = (params.pop if params.last.kind_of? Hash) || {}
56
+ args[:parallel_edges] = true
57
+ super *(params << args)
58
+ end
59
+ end
60
+
61
+ # This is a Digraph that allows for both parallel edges and loops.
62
+ module DirectedMultiGraphBuilder
63
+ include DirectedPseudoGraphBuilder
64
+ extends_host
65
+
66
+ module ClassMethods
67
+ def [](*a)
68
+ self.new.from_array(*a)
69
+ end
70
+ end
71
+
72
+ def initialize(*params)
73
+ args = (params.pop if params.last.kind_of? Hash) || {}
74
+ args[:loops] = true
75
+ super *(params << args)
76
+ end
77
+ end
78
+ end