Module: NetworkX

Defined in:
lib/networkx/others/grid_2d_graph.rb,
lib/networkx/graph.rb,
lib/networkx/digraph.rb,
lib/networkx/version.rb,
lib/networkx/to_matrix.rb,
lib/networkx/flow/utils.rb,
lib/networkx/multigraph.rb,
lib/networkx/others/info.rb,
lib/networkx/multidigraph.rb,
lib/networkx/others/reads.rb,
lib/networkx/operators/all.rb,
lib/networkx/others/bridges.rb,
lib/networkx/traversals/bfs.rb,
lib/networkx/traversals/dfs.rb,
lib/networkx/operators/unary.rb,
lib/networkx/flow/edmondskarp.rb,
lib/networkx/flow/preflowpush.rb,
lib/networkx/operators/binary.rb,
lib/networkx/converters/to_csv.rb,
lib/networkx/operators/product.rb,
lib/networkx/others/generators.rb,
lib/networkx/converters/to_json.rb,
lib/networkx/link_analysis/hits.rb,
lib/networkx/shortest_path/astar.rb,
lib/networkx/shortest_path/dense.rb,
lib/networkx/traversals/edge_dfs.rb,
lib/networkx/flow/capacityscaling.rb,
lib/networkx/link_analysis/pagerank.rb,
lib/networkx/shortest_path/weighted.rb,
lib/networkx/auxillary_functions/dag.rb,
lib/networkx/auxillary_functions/mis.rb,
lib/networkx/auxillary_functions/mst.rb,
lib/networkx/shortest_path/unweighted.rb,
lib/networkx/auxillary_functions/cycles.rb,
lib/networkx/auxillary_functions/wiener.rb,
lib/networkx/auxillary_functions/cliques.rb,
lib/networkx/flow/shortestaugmentingpath.rb,
lib/networkx/auxillary_functions/vitality.rb,
lib/networkx/auxillary_functions/union_find.rb,
lib/networkx/auxillary_functions/eccentricity.rb,
lib/networkx/others/number_connected_components.rb

Overview

Defined Under Namespace

Classes: CurrentEdge, DiGraph, GlobalRelabelThreshold, Graph, Level, MultiDiGraph, MultiGraph, UnionFind

Constant Summary collapse

VERSION =
'0.4.0'.freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

._build_flow_dict(graph, residual) ⇒ Object

Returns the flowdict of the graph



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/networkx/flow/capacityscaling.rb', line 88

def self._build_flow_dict(graph, residual)
  flow_dict = {}
  inf = Float::INFINITY

  if graph.multigraph?
    graph.nodes(data: true).each_key do |u|
      flow_dict[u] = {}
      graph.adj[u].each do |v, uv_edges|
        flow_dict[u][v] = uv_edges.transform_values do |e|
          u != v || (e[:capacity] || inf) <= 0 || (e[:weight] || 0) >= 0 ? 0 : e[:capacity]
        end
      end
      residual.adj[u].each do |v, uv_edges|
        flow_dict[u][v].merge!(uv_edges.to_h do |_, val|
                                 [val[:temp_key][0], val[:flow]] if (val[:flow]).positive?
                               end)
      end
    end
  else
    graph.nodes(data: true).each_key do |u|
      flow_dict[u] = graph.adj[u].to_h do |v, e|
        [v, u != v || (e[:capacity] || inf) <= 0 || (e[:weight] || 0) >= 0 ? 0 : e[:capacity]]
      end
      merge_dict = {}
      residual.adj[u].each do |v, uv_edges|
        uv_edges.each_value { |attrs| merge_dict[v] = attrs[:flow] if (attrs[:flow]).positive? }
      end
      flow_dict[u].merge!(merge_dict)
    end
  end
  flow_dict
end

._build_residual_network(graph) ⇒ Object

Returns the residual graph of the given graph

Raises:

  • (ArgumentError)


42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/networkx/flow/capacityscaling.rb', line 42

def self._build_residual_network(graph)
  raise ArgumentError, 'Sum of demands should be 0!' \
    unless graph.nodes(data: true).values.map { |attr| attr[:demand] || 0 }.sum.zero?

  residual = NetworkX::MultiDiGraph.new(inf: 0)
  residual.add_nodes(graph.nodes(data: true).map { |u, attr| [u, {excess: (attr[:demand] || 0) * -1, potential: 0}] })
  inf = Float::INFINITY
  edge_list = []

  # TODO: Selfloop edges check

  if graph.multigraph?
    graph.adj.each do |u, u_edges|
      u_edges.each do |v, uv_edges|
        uv_edges.each do |k, attrs|
          edge_list << [u, v, k, e] if u != v && (attrs[:capacity] || inf).positive?
        end
      end
    end
  else
    graph.adj.each do |u, u_edges|
      u_edges.each do |v, attrs|
        edge_list << [u, v, 0, attrs] if u != v && (attrs[:capacity] || inf).positive?
      end
    end
  end

  temp_inf = [
    residual.nodes(data: true).map { |_u, attrs| attrs[:excess].abs }.sum,
    edge_list.map {|_, _, _, e| (e.has_key?(:capacity) && e[:capacity] != inf ? e[:capacity] : 0) }.sum * 2
  ].max

  inf = temp_inf.zero? ? 1 : temp_inf

  edge_list.each do |u, v, k, e|
    r = [e[:capacity] || inf, inf].min
    w = e[:weight] || 0
    residual.add_edge(u, v, temp_key: [k, true], capacity: r, weight: w, flow: 0)
    residual.add_edge(v, u, temp_key: [k, false], capacity: 0, weight: -w, flow: 0)
  end
  residual.graph[:inf] = inf
  _detect_unboundedness(residual)
  residual
end

._detect_unboundedness(residual) ⇒ Object

Detects the unboundedness in the residual graph

Raises:

  • (ArgumentError)


26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/networkx/flow/capacityscaling.rb', line 26

def self._detect_unboundedness(residual)
  g = NetworkX::DiGraph.new
  g.add_nodes(residual.nodes(data: true).keys.zip(residual.nodes(data: true).values))
  inf = residual.graph[:inf]

  residual.nodes(data: true).each do |u, _attr|
    residual.adj[u].each do |v, uv_attrs|
      w = inf
      uv_attrs.each { |_key, edge_attrs| w = [w, edge_attrs[:weight]].min if edge_attrs[:capacity] == inf }
      g.add_edge(u, v, weight: w) unless w == inf
    end
  end
  raise ArgumentError, 'Negative cost cycle of infinite capacity found!' if negative_edge_cycle(g)
end

.activate(node, source, target, levels, residual_nodes) ⇒ Object

Helper function to move a node from inactive set to active set



119
120
121
122
123
124
125
126
# File 'lib/networkx/flow/preflowpush.rb', line 119

def self.activate(node, source, target, levels, residual_nodes)
  return if node == source || node == target
  return unless level.inactive.include?(node)

  level = levels[residual_nodes[node][:height]]
  level.inactive.delete(node)
  level.active.add(node)
end

.all_pairs_dijkstra(graph, cutoff = nil) ⇒ Array<Object, Array<Hash{ Object => Numeric }, Hash{ Object => Array<Object> }>>

Finds shortest weighted paths and lengths between all nodes

Parameters:

  • graph (Graph, DiGrhelp_dijkaph, MultiGraph, MultiDiGraph)

    a graph

  • cutoff (Numeric, nil) (defaults to: nil)

    cutoff for the dijkstra algorithm

Returns:

  • (Array<Object, Array<Hash{ Object => Numeric }, Hash{ Object => Array<Object> }>>)

    paths and path lengths between all nodes



189
190
191
192
193
# File 'lib/networkx/shortest_path/weighted.rb', line 189

def self.all_pairs_dijkstra(graph, cutoff = nil)
  path = []
  graph.nodes(data: true).each_key { |n| path << [n, singlesource_dijkstra(graph, n, nil, cutoff)] }
  path
end

.all_pairs_dijkstra_path(graph, cutoff = nil) ⇒ Array<Object, Hash{ Object => Array<Object> }>

Finds shortest weighted paths between all nodes

Parameters:

Returns:

  • (Array<Object, Hash{ Object => Array<Object> }>)

    path lengths between all nodes



213
214
215
216
217
# File 'lib/networkx/shortest_path/weighted.rb', line 213

def self.all_pairs_dijkstra_path(graph, cutoff = nil)
  paths = []
  graph.nodes(data: true).each_key { |n| paths << singlesource_dijkstra_path(graph, n, cutoff) }
  paths
end

.all_pairs_dijkstra_path_length(graph, cutoff = nil) ⇒ Array<Object, Hash{ Object => Numeric }>

Finds shortest weighted path length between all nodes

Parameters:

Returns:

  • (Array<Object, Hash{ Object => Numeric }>)

    path lengths between all nodes



201
202
203
204
205
# File 'lib/networkx/shortest_path/weighted.rb', line 201

def self.all_pairs_dijkstra_path_length(graph, cutoff = nil)
  path_lengths = []
  graph.nodes(data: true).each_key { |n| path_lengths << [n, singlesource_dijkstra_path_length(graph, n, cutoff)] }
  path_lengths
end

.all_pairs_shortest_path(graph, cutoff = nil) ⇒ Array<Object, Hash {Object => Array<Object> }>

Computes shortest paths to all nodes from all nodes

Parameters:

Returns:

  • (Array<Object, Hash {Object => Array<Object> }>)

    paths for all nodes from all nodes



94
95
96
97
98
# File 'lib/networkx/shortest_path/unweighted.rb', line 94

def self.all_pairs_shortest_path(graph, cutoff = nil)
  shortest_paths = []
  graph.nodes(data: true).each_key { |n| shortest_paths << [n, single_source_shortest_path(graph, n, cutoff)] }
  shortest_paths
end

.all_pairs_shortest_path_length(graph, cutoff = nil) ⇒ Array<Object, Array<Object, Numeric>>

Computes shortest path values to all nodes from all nodes

Parameters:

Returns:

  • (Array<Object, Array<Object, Numeric>>)

    path lengths for all nodes from all nodes



46
47
48
49
50
# File 'lib/networkx/shortest_path/unweighted.rb', line 46

def self.all_pairs_shortest_path_length(graph, cutoff = nil)
  shortest_paths = []
  graph.nodes(data: true).each_key { |n| shortest_paths << [n, single_source_shortest_path_length(graph, n, cutoff)] }
  shortest_paths
end

.allpairs_bellmanford_path(graph, cutoff = nil) ⇒ Array<Object, Hash{ Object => Array<Object> }>

Shortest paths between all nodes using Bellman Ford algorithm

Parameters:

Returns:

  • (Array<Object, Hash{ Object => Array<Object> }>)

    path lengths from source to all nodes



373
374
375
376
377
# File 'lib/networkx/shortest_path/weighted.rb', line 373

def self.allpairs_bellmanford_path(graph, cutoff = nil)
  paths = []
  graph.nodes(data: true).each_key { |n| paths << [n, singlesource_bellmanford_path(graph, n, cutoff)] }
  paths
end

.allpairs_bellmanford_path_length(graph, cutoff = nil) ⇒ Array<Object, Hash{ Object => Numeric }>

Shortest path lengths between all nodes using Bellman Ford algorithm

Parameters:

Returns:

  • (Array<Object, Hash{ Object => Numeric }>)

    path lengths from source to all nodes



361
362
363
364
365
# File 'lib/networkx/shortest_path/weighted.rb', line 361

def self.allpairs_bellmanford_path_length(graph, cutoff = nil)
  path_lengths = []
  graph.nodes(data: true).each_key { |n| path_lengths << [n, singlesource_bellmanford_path_length(graph, n, cutoff)] }
  path_lengths
end

.ancestors(graph, source) ⇒ Array<Object>

Returns the ancestors of a given node

Parameters:

Returns:

  • (Array<Object>)

    Array of the ancestors

Raises:

  • (ArgumentError)


21
22
23
24
25
26
# File 'lib/networkx/auxillary_functions/dag.rb', line 21

def self.ancestors(graph, source)
  raise ArgumentError, 'Source is not present in the graph!' unless graph.node?(source)

  anc = single_source_shortest_path_length(graph.reverse, source).map { |u, _| u }.uniq
  anc - [source]
end

.arbitrary_element(iterable) ⇒ Object

Helper function to return an arbitrary element from an iterable object



3
4
5
6
7
8
9
10
11
# File 'lib/networkx/flow/preflowpush.rb', line 3

def self.arbitrary_element(iterable)
  if iterable.is_a?(Hash)
    iterable.first[0]
  elsif iterable.respond_to?(:first)
    iterable.first
  elsif iterable.respond_to?(:[])
    iterable[0]
  end
end

.astar_path(graph, source, target, heuristic = nil) ⇒ Array<Object>

Returns path using astar algorithm between source and target

Parameters:

  • graph (Graph, DiGraph, MultiGraph, MultiDiGraph)

    a graph

  • source (Object)

    a node to start astar from

  • target (Object)

    a node to end astar

  • heuristic (defaults to: nil)

    [] a lambda to compute heuristic b/w two nodes

Returns:

  • (Array<Object>)

    an array of nodes forming a path between source and target

Raises:

  • (ArgumentError)


11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/networkx/shortest_path/astar.rb', line 11

def self.astar_path(graph, source, target, heuristic = nil)
  warn 'A* is not implemented for MultiGraph and MultiDiGraph' if graph.is_a?(MultiGraph) || graph.is_a?(MultiDiGraph)

  raise ArgumentError, 'Either source or target is not in graph' unless graph.node?(source) && graph.node?(target)

  count = ->(i) { i + 1 }
  i = -1
  heuristic ||= (->(_u, _v) { 0 })
  heap = Heap.new { |x, y| x[0] < y[0] || (x[0] == y[0] && x[1] < y[1]) }
  heap << [0, count.call(i), source, 0, nil]
  enqueued, explored = {}, {}

  until heap.empty?
    _, _, curnode, dist, parent = heap.pop
    if curnode == target
      path = [curnode]
      node = parent
      until node.nil?
        path << node
        node = explored[node]
      end
      path.reverse
      return path
    end

    next if explored.has_key?(curnode)

    explored[curnode] = parent

    graph.adj[curnode].each do |u, attrs|
      next if explored.has_key?(u)

      ncost = dist + (attrs[:weight] || 1)
      if enqueued.has_key?(u)
        qcost, = enqueued[u]
        next if qcost <= ncost
      else
        h = heuristic.call(u, target)
        enqueued[u] = ncost, h
        heap << [ncost + h, count.call(i), u, ncost, curnode]
      end
    end
  end
  raise ArgumentError, 'Target not reachable!'
end

.astar_path_length(graph, source, target, heuristic = nil) ⇒ Numeric

Returns astar path length b/w source and target

Parameters:

  • graph (Graph, DiGraph, MultiGraph, MultiDiGraph)

    a graph

  • source (Object)

    a node to start astar from

  • target (Object)

    a node to end astar

  • heuristic (defaults to: nil)

    [] a lambda to compute heuristic b/w two nodes

Returns:

  • (Numeric)

    the length of the path

Raises:

  • (ArgumentError)


65
66
67
68
69
70
71
72
# File 'lib/networkx/shortest_path/astar.rb', line 65

def self.astar_path_length(graph, source, target, heuristic = nil)
  raise ArgumentError, 'Either source or target is not in graph' unless graph.node?(source) && graph.node?(target)

  path = astar_path(graph, source, target, heuristic)
  path_length = 0
  (1..(path.length - 1)).each { |i| path_length += (graph.adj[path[i - 1]][path[i]][:weight] || 1) }
  path_length
end

.augment(residual, inf, path) ⇒ Object

Helper function to augment the flow in a residual graph

Raises:

  • (ArgumentError)


3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# File 'lib/networkx/flow/edmondskarp.rb', line 3

def self.augment(residual, inf, path)
  flow = inf
  path_first_elem = path.shift
  u = path_first_elem
  path.each do |v|
    flow = [flow, residual.adj[u][v][:capacity] - residual.adj[u][v][:flow]].min
    u = v
  end
  raise ArgumentError, 'Infinite capacity path!' if flow * 2 > inf

  u = path_first_elem
  path.each do |v|
    residual.adj[u][v][:flow] += flow
    residual.adj[v][u][:flow] -= flow
    u = v
  end
  flow
end

.authority_matrix(graph) ⇒ Matrix

Computes authority matrix for the graph

Parameters:

Returns:

  • (Matrix)

    authority matrix for the graph



45
46
47
48
# File 'lib/networkx/link_analysis/hits.rb', line 45

def self.authority_matrix(graph)
  matrix, = to_matrix(graph, 0)
  matrix.transpose * matrix
end

.bellmanford_path(graph, source, target) ⇒ Array<Object>

Shortest path from source to target using Bellman Ford algorithm

Parameters:

Returns:

  • (Array<Object>)

    path from source to target



326
327
328
329
# File 'lib/networkx/shortest_path/weighted.rb', line 326

def self.bellmanford_path(graph, source, target)
  _, path = singlesource_bellmanford(graph, source, target)
  path
end

.bellmanford_path_length(graph, source, target) ⇒ Numeric

Length of shortest path from source to target using Bellman Ford algorithm

Parameters:

Returns:

  • (Numeric)

    distance between source and target

Raises:

  • (ArgumentError)


309
310
311
312
313
314
315
316
317
# File 'lib/networkx/shortest_path/weighted.rb', line 309

def self.bellmanford_path_length(graph, source, target)
  return 0 if source == target

  weight = get_weight(graph)
  length = help_bellman_ford(graph, [source], weight, nil, nil, nil, nil, target)
  raise ArgumentError, 'Node not reachable!' unless length.has_key?(target)

  length[target]
end

.bellmanford_predecesor_distance(graph, source, target = nil, cutoff = nil) ⇒ Array<Hash{ Object => Array<Object> }, Hash{ Object => Numeric }>

Finds shortest weighted path lengths and predecessors on shortest paths

Parameters:

  • graph (Graph, DiGraph, MultiGraph, MultiDiGraph)

    a graph

  • source (Object)

    source

  • target (Object, nil) (defaults to: nil)

    target

  • cutoff (Numeric, nil) (defaults to: nil)

    cutoff for the dijkstra algorithm

Returns:

  • (Array<Hash{ Object => Array<Object> }, Hash{ Object => Numeric }>)

    predecessors and distances

Raises:

  • (ArgumentError)


277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/networkx/shortest_path/weighted.rb', line 277

def self.bellmanford_predecesor_distance(graph, source, target = nil, cutoff = nil)
  raise ArgumentError, 'Node not found!' unless graph.node?(source)

  weight = get_weight(graph)
  # TODO: Detection of selfloop edges
  dist = {source => 0}
  pred = {source => []}
  return [pred, dist] if graph.nodes(data: true).length == 1

  dist = help_bellman_ford(graph, [source], weight, pred, nil, dist, cutoff, target)
  [pred, dist]
end

.bfs_edges(graph, source) ⇒ Object

Returns edges of the graph travelled in breadth first fashion

Examples:

NetworkX.bfs_edges(graph, source)

Parameters:

Raises:

  • (KeyError)


9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/networkx/traversals/bfs.rb', line 9

def self.bfs_edges(graph, source)
  raise KeyError, "There exists no node names #{source} in the given graph." unless graph.node?(source)

  bfs_edges = []
  visited = [source]
  queue = Queue.new.push([source, graph.neighbours(source)])
  until queue.empty?
    parent, children = queue.pop
    children.each_key do |child|
      next if visited.include?(child)

      bfs_edges << [parent, child]
      visited << child
      queue.push([child, graph.neighbours(child)])
    end
  end
  bfs_edges
end

.bfs_predecessors(graph, source) ⇒ Object

Returns predecessor child pair of the graph travelled in breadth first fashion

Examples:

NetworkX.bfs_predecessors(graph, source)

Parameters:



52
53
54
55
56
57
# File 'lib/networkx/traversals/bfs.rb', line 52

def self.bfs_predecessors(graph, source)
  bfs_edges = bfs_edges(graph, source)
  predecessors = {}
  bfs_edges.each { |u, v| predecessors[v] = u }
  predecessors
end

.bfs_successors(graph, source) ⇒ Object

Returns parent successor pair of the graph travelled in breadth first fashion

Examples:

NetworkX.bfs_successors(graph, source)

Parameters:



35
36
37
38
39
40
41
42
43
# File 'lib/networkx/traversals/bfs.rb', line 35

def self.bfs_successors(graph, source)
  bfs_edges = bfs_edges(graph, source)
  successors = {}
  bfs_edges.each do |u, v|
    successors[u] = [] if successors[u].nil?
    successors[u] << v
  end
  successors
end

.bidirectional_bfs(residual, source, target) ⇒ Object

Helper function for the bidirectional bfs



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/networkx/flow/edmondskarp.rb', line 23

def self.bidirectional_bfs(residual, source, target)
  pred, succ = {source => nil}, {target => nil}
  q_s, q_t = [source], [target]
  loop do
    q = []
    if q_s.length <= q_t.length
      q_s.each do |u|
        residual.adj[u].each do |v, uv_attrs|
          next unless !pred.include?(v) && (uv_attrs[:flow] < uv_attrs[:capacity])

          pred[v] = u
          return [v, pred, succ] if succ.has_key?(v)

          q << v
        end
      end
      return [nil, nil, nil] if q.empty?

      q_s = q
    else
      q_t.each do |u|
        residual.pred[u].each do |v, uv_attrs|
          next unless !succ.has_key?(v) && uv_attrs[:flow] < uv_attrs[:capacity]

          succ[v] = u
          return [v, pred, succ] if pred.has_key?(v)

          q << v
        end
      end
      return [nil, nil, nil] if q.empty?

      q_t = q
    end
  end
end

.bridges(graph) ⇒ [Object, Object]

Returns bridges.

Parameters:

  • graph (Graph)

    Graph

Returns:

  • ([Object, Object])

    bridges



7
8
9
# File 'lib/networkx/others/bridges.rb', line 7

def self.bridges(graph)
  each_bridge(graph).to_a
end

.build_flow_dict(graph, residual) ⇒ Hash{ Object => Hash{ Object => Numeric }] flowdict containing all the flow values in the edges

Build flow dictionary of a graph from its residual graph

Parameters:

Returns:

  • (Hash{ Object => Hash{ Object => Numeric }] flowdict containing all the flow values in the edges)

    Hash{ Object => Hash{ Object => Numeric }] flowdict containing all the flow values in the edges



130
131
132
133
134
135
136
137
138
# File 'lib/networkx/flow/utils.rb', line 130

def self.build_flow_dict(graph, residual)
  flow_dict = {}
  graph.edges.each do |u, u_edges|
    flow_dict[u] = {}
    u_edges.each_key { |v| flow_dict[u][v] = 0 }
    u_edges.each_key { |v| flow_dict[u][v] = residual[u][v][:flow] if (residual[u][v][:flow]).positive? }
  end
  flow_dict
end

.build_residual_network(graph) ⇒ DiGraph

Builds a residual graph from a constituent graph

Parameters:

Returns:

Raises:

  • (NotImplementedError)


61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/networkx/flow/utils.rb', line 61

def self.build_residual_network(graph)
  raise NotImplementedError, 'MultiGraph and MultiDiGraph not supported!' if graph.multigraph?

  r_network = NetworkX::DiGraph.new(inf: 0, flow_value: 0)
  r_network.add_nodes(graph.nodes(data: true).keys)
  inf = Float::INFINITY
  edge_list = []

  graph.adj.each do |u, u_edges|
    u_edges.each do |v, uv_attrs|
      edge_list << [u, v, uv_attrs] if (uv_attrs[:capacity] || inf).positive? && u != v
    end
  end

  inf_chk = 3 * edge_list.inject(0) do |result, arr|
    arr[2].has_key?(:capacity) && arr[2][:capacity] != inf ? (result + arr[2][:capacity]) : result
  end
  inf = inf_chk.zero? ? 1 : inf_chk

  if graph.directed?
    edge_list.each do |u, v, attrs|
      r = [attrs[:capacity] || inf, inf].min
      if r_network.adj[u][v].nil?
        r_network.add_edge(u, v, capacity: r)
        r_network.add_edge(v, u, capacity: 0)
      else
        r_network[u][v][:capacity] = r
      end
    end
  else
    edge_list.each do |u, v, attrs|
      r = [attrs[:capacity] || inf, inf].min
      r_network.add_edge(u, v, capacity: r)
      r_network.add_edge(v, u, capacity: r)
    end
  end
  r_network.graph[:inf] = inf
  r_network
end

.capacity_scaling(graph) ⇒ Array<Numeric, Hash{ Object => Hash{ Object => Numeric } }>

Computes max flow using capacity scaling algorithm

Parameters:

Returns:

  • (Array<Numeric, Hash{ Object => Hash{ Object => Numeric } }>)

    flow cost and flowdict containing all the flow values in the edges



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/networkx/flow/capacityscaling.rb', line 127

def self.capacity_scaling(graph)
  residual = _build_residual_network(graph)
  inf = Float::INFINITY
  flow_cost = 0

  # TODO: Account cost of self-loof edges

  wmax = ([-inf] + residual.adj.each_with_object([]) do |u, arr|
                     u[1].each { |_, key_attrs| key_attrs.each { |_, attrs| arr << attrs[:capacity] } }
                   end).max

  return flow_cost, _build_flow_dict(graph, residual) if wmax == -inf

  r_nodes = residual.nodes
  r_adj = residual.adj

  delta = 2**Math.log2(wmax).floor
  while delta >= 1
    r_nodes.each do |u, u_attrs|
      p_u = u_attrs[:potential]
      r_adj[u].each do |v, uv_edges|
        uv_edges.each do |_k, e|
          flow = e[:capacity]
          next unless (e[:weight] - p_u + r_nodes[v][:potential]).negative?

          flow = e[:capacity] - e[:flow]
          next unless flow >= delta

          e[:flow] += flow
          r_adj[v][u].each_key do |val|
            val[:flow] += val[:temp_key][0] == e[:temp_key][0] && val[:temp_key][1] != e[:temp_key][1] ? -flow : 0
          end
          r_nodes[u][:excess] -= flow
          r_nodes[v][:excess] += flow
        end
      end
    end

    s_set = Set.new
    t_set = Set.new

    residual.nodes(data: true).each do |u, _attrs|
      excess = r_nodes[u][:excess]
      if excess >= delta
        s_set.add(u)
      elsif excess <= -delta
        t_set.add(u)
      end
    end

    while !s_set.empty? && !t_set.empty?
      s = arbitrary_element
      t = nil
      d = {}
      pred = {s => nil}
      h = Heap.new { |x, y| x[0] < y[0] || (x[0] == y[0] && x[1] < y[1]) }
      h_dict = {s => 0}
      h << [0, count, s]
      until h.empty?
        d_u, _, u = h.pop
        h_dict.delete(u)
        d[u] = d_u
        if t_set.include?(u)
          t = u
          break
        end
        p_u = r_nodes[u][:potential]
        r_adj[u].each do |v, uv_edges|
          next if d.has_key?(v)

          wmin = inf
          uv_edges.each_value do |e|
            next unless e[:capacity] - e[:flow] >= delta

            w = e[:weight]
            next unless w < wmin

            wmin = w
          end
          next if wmin == inf

          d_v = d_u + wmin - p_u + r_nodes[v][:potential]
          next unless h_dict[v] > d_v

          h << [d_v, count, v]
          h_dict[v] = d_v
          pred[v] = [u, kmin, emin]
        end
      end

      if t.nil?
        s_set.delete(s)
      else
        while u != s
          v = u
          u, k, e = pred[v]
          e[:flow] += delta
          r_adj[v][u].each_key do |val|
            val[:flow] += val[:temp_key][0] == k[0] && val[:temp_key][1] != k[1] ? -delta : 0
          end
        end
        r_nodes[s][:excess] -= delta
        r_nodes[t][:excess] += delta
        s_set.delete(s) if r_nodes[s][:excess] < delta
        t_set.delete(t) if r_nodes[t][:excess] > -delta
        d_t = d[t]
        d.each { |node, d_u_node| r_nodes[node][:potential] -= (d_u_node - d_t) }
      end
    end
    delta = (delta / 2).floor
  end

  r_nodes.each_value { |attrs| raise ArgumentError, 'No flow satisfying all demands!' if attrs[:excess] != 0 }

  residual.nodes(data: true).each_key do |node|
    residual.adj[node].each_value do |uv_edges|
      uv_edges.each_value do |k_attrs|
        flow = k_attrs[:flow]
        flow_cost += (flow * k_attrs[:weight])
      end
    end
  end
  [flow_cost, _build_flow_dict(graph, residual)]
end

.cartesian_product(g1, g2) ⇒ Graph, ...

Returns the cartesian product of two graphs

Parameters:

Returns:



129
130
131
132
133
134
135
# File 'lib/networkx/operators/product.rb', line 129

def self.cartesian_product(g1, g2)
  g = init_product_graph(g1, g2)
  g.add_nodes(node_product(g1, g2))
  g.add_edges(edges_cross_nodes(g1, g2))
  g.add_edges(nodes_cross_edges(g1, g2))
  g
end

.closeness_vitality(graph, node) ⇒ Numeric

Returns the closeness vitality of a node

Parameters:

  • graph (Graph, DiGraph)

    a graph

  • node (Object)

    node to compute closeness vitality of

Returns:

  • (Numeric)

    closeness vitality of the given node



8
9
10
11
12
# File 'lib/networkx/auxillary_functions/vitality.rb', line 8

def self.closeness_vitality(graph, node)
  before = wiener_index(graph)
  after = wiener_index(graph.subgraph(graph.nodes(data: true).keys - [node]))
  before - after
end

.complement(graph) ⇒ Graph, ...

Performs the complement operation on the graph

Parameters:

Returns:



7
8
9
10
11
12
13
14
15
16
# File 'lib/networkx/operators/unary.rb', line 7

def self.complement(graph)
  result = Marshal.load(Marshal.dump(graph))
  result.clear

  result.add_nodes(graph.nodes(data: true).map { |u, attrs| [u, attrs] })
  graph.adj.each do |u, u_edges|
    graph.nodes(data: true).each { |v, attrs| result.add_edge(u, v, **attrs) if !u_edges.has_key?(v) && u != v }
  end
  result
end

.compose(g1, g2) ⇒ Graph, ...

Performs the composition of two graphs

Parameters:

Returns:

Raises:

  • (ArgumentError)


173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/networkx/operators/binary.rb', line 173

def self.compose(g1, g2)
  result = g1.class.new

  raise ArgumentError, 'Arguments must be both Graphs or MultiGraphs!' unless g1.multigraph? == g2.multigraph?

  result.add_nodes(g1.nodes(data: true).map { |u, attrs| [u, attrs] })
  result.add_nodes(g2.nodes(data: true).map { |u, attrs| [u, attrs] })

  if g1.multigraph?
    g1.adj.each { |u, e| e.each { |v, uv_edges| uv_edges.each_value { |attrs| result.add_edge(u, v, **attrs) } } }
    g2.adj.each { |u, e| e.each { |v, uv_edges| uv_edges.each_value { |attrs| result.add_edge(u, v, **attrs) } } }
  else
    g1.adj.each { |u, u_edges| u_edges.each { |v, attrs| result.add_edge(u, v, **attrs) } }
    g2.adj.each { |u, u_edges| u_edges.each { |v, attrs| result.add_edge(u, v, **attrs) } }
  end
  result
end

.compose_all(graphs) ⇒ Graph, ...

Performs the composition of many graphs

Parameters:

Returns:

Raises:

  • (ArgumentError)


55
56
57
58
59
60
61
62
63
64
# File 'lib/networkx/operators/all.rb', line 55

def self.compose_all(graphs)
  raise ArgumentError, 'Argument array is empty' if graphs.empty?

  result = graphs.shift

  graphs.each do |graph|
    result = NetworkX.compose(result, graph)
  end
  result
end

.convert_to_distinct_labels(graph, starting_int = -1)) ⇒ Object

Transforms the labels of the nodes of the graphs so that they are disjoint.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/networkx/operators/binary.rb', line 27

def self.convert_to_distinct_labels(graph, starting_int = -1)
  new_graph = graph.class.new

  idx_dict = graph.nodes(data: true).keys.to_h do |v|
    starting_int += 1
    [v, starting_int]
  end

  graph.nodes(data: true).each do |u, attrs|
    new_graph.add_node(u.to_s + idx_dict[u].to_s, **attrs)
  end

  graph.adj.each do |u, u_edges|
    u_edges.each do |v, uv_attrs|
      if graph.multigraph?
        uv_attrs.each do |_k, attrs|
          new_graph.add_edge(u.to_s + idx_dict[u].to_s, v.to_s + idx_dict[v].to_s, **attrs)
        end
      else
        new_graph.add_edge(u.to_s + idx_dict[u].to_s, v.to_s + idx_dict[v].to_s, **uv_attrs)
      end
    end
  end
  new_graph
end

.cycle?(undirected_graph) ⇒ book

Returns whether the given undirected cycle has cycle.

Parameters:

  • undirected_graph (Graph)

    an undirected graph

Returns:

  • (book)

    true if the given graph has cycle. otherwise, false.



107
108
109
110
111
112
113
# File 'lib/networkx/auxillary_functions/cycles.rb', line 107

def self.cycle?(undirected_graph)
  uf = NetworkX::UnionFind.new
  undirected_graph.edges.each do |x, y|
    uf[x] == uf[y] ? (return [x, y]) : uf.unite(x, y)
  end
  false
end

.cycle_basis(graph, root = nil) ⇒ Array<Array<Object>>

Returns all basis cycles in graph

Parameters:

  • graph (Graph)

    a graph

  • root (Object, Nil) (defaults to: nil)

    root for the graph cycles

Returns:

  • (Array<Array<Object>>)

    Arrays of nodes in the cycles



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/networkx/auxillary_functions/cycles.rb', line 8

def self.cycle_basis(graph, root = nil)
  gnodes = graph.nodes(data: true).keys
  cycles = []
  until gnodes.empty?
    root = gnodes.shift if root.nil?
    stack = [root]
    pred = {root => root}
    used = {root => []}
    until stack.empty?
      z = stack.shift
      zused = used[z]
      graph.adj[z].each_key do |u|
        if !used.has_key?(u)
          pred[u] = z
          stack << u
          used[u] = [z]
        elsif u == z
          cycles << [z]
        elsif !zused.include?(u)
          pn = used[u]
          cycle = [u, z]
          p = pred[z]
          until pn.include?(p)
            cycle << p
            p = pred[p]
          end
          cycle << p
          cycles << cycle
          used[u] << z
          used[u] = used[u].uniq
        end
      end
    end
    gnodes -= pred.keys
    root = nil
  end
  cycles
end

.descendants(graph, source) ⇒ Array<Object>

Returns the descendants of a given node

Parameters:

Returns:

  • (Array<Object>)

    Array of the descendants

Raises:

  • (ArgumentError)


8
9
10
11
12
13
# File 'lib/networkx/auxillary_functions/dag.rb', line 8

def self.descendants(graph, source)
  raise ArgumentError, 'Source is not present in the graph!' unless graph.node?(source)

  des = single_source_shortest_path_length(graph, source).map { |u, _| u }.uniq
  des - [source]
end

.detect_unboundedness(r_network, source, target) ⇒ Object

Detects unboundedness in a graph, raises exception when infinite capacity flow is found

Parameters:

  • r_network (DiGraph)

    a residual graph

  • source (Object)

    source node

  • target (Object)

    target node



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/networkx/flow/utils.rb', line 107

def self.detect_unboundedness(r_network, source, target)
  q = [source]
  seen = Set.new([source])
  inf = r_network.graph[:inf]
  until q.empty?
    u = q.shift
    r_network.adj[u].each do |v, uv_attrs|
      next unless uv_attrs[:capacity] == inf && !seen.include?(v)
      raise ArgumentError, 'Infinite capacity flow!' if v == target

      seen << v
      q << v
    end
  end
end

.dfs_edges(graph, source, depth_limit = nil) ⇒ Object

Returns edges of the graph travelled in depth first fashion

Examples:

NetworkX.dfs_edges(graph, source)

Parameters:

  • graph (Graph, DiGraph, MultiGraph, MultiDiGraph)

    a graph

  • source (Object)

    node to start dfs from

  • depth_limit (Integer, nil) (defaults to: nil)

    the depth limit of dfs

Raises:

  • (KeyError)


10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/networkx/traversals/dfs.rb', line 10

def self.dfs_edges(graph, source, depth_limit = nil)
  raise KeyError, "There exists no node names #{source} in the given graph." unless graph.node?(source)

  depth_limit += 1 if depth_limit
  depth_limit = graph.nodes(data: true).length if depth_limit.nil?
  dfs_edges = []
  visited = [source]
  stack = [[-1, source, depth_limit, graph.neighbours(source)]]
  until stack.empty?
    earlier_node, parent, depth_now, children = stack.pop
    dfs_edges << [earlier_node, parent]
    children.each_key do |child|
      unless visited.include?(child)
        visited << child
        stack.push([parent, child, depth_now - 1, graph.neighbours(child)]) if depth_now > 1
      end
    end
  end
  dfs_edges.shift
  dfs_edges
end

.dfs_predecessors(graph, source, depth_limit = nil) ⇒ Object

Returns predecessor child pair of the graph travelled in depth first fashion

Examples:

NetworkX.dfs_predecessors(graph, source)

Parameters:

  • graph (Graph, DiGraph, MultiGraph, MultiDiGraph)

    a graph

  • source (Object)

    node to start dfs from

  • depth_limit (Integer, nil) (defaults to: nil)

    the depth limit of dfs



73
74
75
76
77
78
# File 'lib/networkx/traversals/dfs.rb', line 73

def self.dfs_predecessors(graph, source, depth_limit = nil)
  dfs_edges = dfs_edges(graph, source, depth_limit)
  predecessors = {}
  dfs_edges.each { |u, v| predecessors[v] = u }
  predecessors
end

.dfs_successors(graph, source, depth_limit = nil) ⇒ Object

Returns parent successor pair of the graph travelled in depth first fashion

Examples:

NetworkX.dfs_successors(graph, source)

Parameters:

  • graph (Graph, DiGraph, MultiGraph, MultiDiGraph)

    a graph

  • source (Object)

    node to start dfs from

  • depth_limit (Integer, nil) (defaults to: nil)

    the depth limit of dfs



55
56
57
58
59
60
61
62
63
# File 'lib/networkx/traversals/dfs.rb', line 55

def self.dfs_successors(graph, source, depth_limit = nil)
  dfs_edges = dfs_edges(graph, source, depth_limit)
  successors = {}
  dfs_edges.each do |u, v|
    successors[u] = [] if successors[u].nil?
    successors[u] << v
  end
  successors
end

.dfs_tree(graph, source, depth_limit = nil) ⇒ Object

Returns dfs tree of the graph

Examples:

NetworkX.dfs_tree(graph, source)

Parameters:

  • graph (Graph, DiGraph, MultiGraph, MultiDiGraph)

    a graph

  • source (Object)

    node to start dfs from

  • depth_limit (Integer, nil) (defaults to: nil)

    the depth limit of dfs



40
41
42
43
44
45
# File 'lib/networkx/traversals/dfs.rb', line 40

def self.dfs_tree(graph, source, depth_limit = nil)
  t = NetworkX::DiGraph.new
  t.add_node(source)
  t.add_edges_from(dfs_edges(graph, source, depth_limit))
  t
end

.diameter(graph) ⇒ Numeric

Returns the diameter of a graph

Parameters:

Returns:

  • (Numeric)

    diameter of the graph



25
26
27
# File 'lib/networkx/auxillary_functions/eccentricity.rb', line 25

def self.diameter(graph)
  eccentricity(graph).values.max
end

.difference(g1, g2) ⇒ Graph, ...

Performs the difference of two graphs

Parameters:

Returns:

Raises:

  • (ArgumentError)


93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/networkx/operators/binary.rb', line 93

def self.difference(g1, g2)
  result = g1.class.new

  raise ArgumentError, 'Arguments must be both Graphs or MultiGraphs!' unless g1.multigraph? == g2.multigraph?

  unless (g1.nodes(data: true).keys - g2.nodes(data: true).keys).empty?
    raise ArgumentError, 'Node sets must be equal!'
  end

  g1.nodes(data: true).each { |u, attrs| result.add_node(u, **attrs) }

  g1.adj.each do |u, u_edges|
    u_edges.each do |v, uv_attrs|
      if g1.multigraph?
        next if u > v && g1.instance_of?(MultiGraph)

        uv_attrs.each do |k, attrs|
          result.add_edge(u, v, **attrs) unless g2.edge?(u, v, k)
        end
      else
        result.add_edge(u, v, **uv_attrs) unless g2.edge?(u, v)
      end
    end
  end
  result
end

.dijkstra_path(graph, source, target) ⇒ Numeric

Computes shortest path to target from the given node

Parameters:

Returns:

  • (Numeric)

    path for target node from given node



163
164
165
166
# File 'lib/networkx/shortest_path/weighted.rb', line 163

def self.dijkstra_path(graph, source, target)
  _, path = singlesource_dijkstra(graph, source, target)
  path
end

.dijkstra_path_length(graph, source, target) ⇒ Numeric

Computes shortest path length to target from the given node

Parameters:

Returns:

  • (Numeric)

    path length for target node from given node

Raises:

  • (KeyError)


146
147
148
149
150
151
152
153
154
# File 'lib/networkx/shortest_path/weighted.rb', line 146

def self.dijkstra_path_length(graph, source, target)
  return 0 if source == target

  weight = get_weight(graph)
  length = help_dijkstra(graph, source, weight, nil, nil, nil, target)
  raise KeyError, 'Node not reachable!' unless length.has_key?(target)

  length[target]
end

.dijkstra_predecessor_distance(graph, source, cutoff = nil) ⇒ <Array<Hash{ Object => Array<Object> }, Hash{ Object => Numeric }>] predcessor hash and distance hash

Computes weighted shortest path length and predecessors

Parameters:

Returns:

  • (<Array<Hash{ Object => Array<Object> }, Hash{ Object => Numeric }>] predcessor hash and distance hash)

    <Array<Hash{ Object => Array<Object> }, Hash{ Object => Numeric }>] predcessor hash and distance hash



176
177
178
179
180
# File 'lib/networkx/shortest_path/weighted.rb', line 176

def self.dijkstra_predecessor_distance(graph, source, cutoff = nil)
  weight = get_weight(graph)
  pred = {source => []}
  [pred, help_dijkstra(graph, source, weight, pred, nil, cutoff)]
end

.directed_edges_cross_edges(g1, g2) ⇒ Object

Returns the product of directed edges with edges



40
41
42
43
44
45
46
47
48
# File 'lib/networkx/operators/product.rb', line 40

def self.directed_edges_cross_edges(g1, g2)
  result = []
  edges_in_array(g1).each do |u, v, c|
    edges_in_array(g2).each do |x, y, d|
      result << [[u, x], [v, y], hash_product(c, d)]
    end
  end
  result
end

.discharge(u_node, is_phase1, residual_nodes, residual_adj, height, levels, grt, source, target) ⇒ Object

Helper function for discharging a node



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/networkx/flow/preflowpush.rb', line 135

def self.discharge(u_node, is_phase1, residual_nodes, residual_adj, height, levels, grt, source, target)
  height_val = residual_nodes[u_node][:height]
  curr_edge = residual_nodes[u_node][:curr_edge]
  next_height = height_val
  levels[height_val].active.delete(u_node)

  loop do
    v, attr = curr_edge.get
    if height_val == residual_nodes[v][:height] + 1 && attr[:flow] < attr[:capacity]
      flow = [residual_nodes[u_node][:excess], attr[:capacity] - attr[:flow]].min
      push(u_node, v, flow, residual_nodes, residual_adj)
      activate(v, source, target, levels, residual_nodes)
      if residual_nodes[u_node][:excess].zero?
        levels[height_val].inactive.add(u_node)
        break
      end
    end
    begin
      curr_edge.move_to_next
    rescue StopIteration
      height_val = relabel(u_node, grt, residual_adj, residual_nodes, source, target, levels)
      if is_phase1 && height_val >= n - 1
        levels[height].active.add(u_node)
        break
      end
      next_height = height_val
    end
  end
  residual_nodes[u_node][:height] = height_val
  next_height
end

.disjoint_union(g1, g2) ⇒ Graph, ...

Performs the disjoint union of two graphs

Parameters:

Returns:



225
226
227
228
229
230
231
232
# File 'lib/networkx/operators/binary.rb', line 225

def self.disjoint_union(g1, g2)
  new_g1 = convert_to_distinct_labels(g1)
  new_g2 = convert_to_distinct_labels(g2)
  result = union(new_g1, new_g2)
  result.graph.merge!(g1.graph)
  result.graph.merge!(g2.graph)
  result
end

.disjoint_union_all(graphs) ⇒ Graph, ...

Performs the disjoint union of many graphs

Parameters:

Returns:

Raises:

  • (ArgumentError)


23
24
25
26
27
28
29
30
31
32
# File 'lib/networkx/operators/all.rb', line 23

def self.disjoint_union_all(graphs)
  raise ArgumentError, 'Argument array is empty' if graphs.empty?

  result = graphs.shift

  graphs.each do |graph|
    result = NetworkX.disjoint_union(result, graph)
  end
  result
end

.dist_path_lambda(_graph, _new_weight) ⇒ Object

Helper function to get distances



385
386
387
388
389
390
391
# File 'lib/networkx/shortest_path/weighted.rb', line 385

def self.dist_path_lambda(_graph, _new_weight)
  lambda do |graph, v, new_weight|
    paths = {v => [v]}
    _ = help_dijkstra(graph, v, new_weight, nil, paths)
    paths
  end
end

.each_bridge(graph) ⇒ Object

Parameters:

  • graph (Graph)

    Graph



12
13
14
15
16
17
18
19
20
21
22
# File 'lib/networkx/others/bridges.rb', line 12

def self.each_bridge(graph)
  return enum_for(:each_bridge, graph) unless block_given?

  graph.each_edge.with_index do |(s_i, t_i), i|
    uf = UnionFind.new(1..graph.number_of_nodes)
    graph.each_edge.with_index do |(s_j, t_j), j|
      uf.unite(s_j, t_j) if i != j
    end
    yield [s_i, t_i] unless uf.same?(s_i, t_i)
  end
end

.eccentricity(graph, node = nil) ⇒ Array<Numeric>, Numeric

Returns the eccentricity of a particular node or all nodes

Parameters:

Returns:

  • (Array<Numeric>, Numeric)

    eccentricity/eccentricites of all nodes



8
9
10
11
12
13
14
15
16
17
18
# File 'lib/networkx/auxillary_functions/eccentricity.rb', line 8

def self.eccentricity(graph, node = nil)
  e = {}
  graph.nodes(data: true).each do |u, _|
    length = single_source_shortest_path_length(graph, u)
    l = length.length
    raise ArgumentError, 'Found infinite path length!' unless l == graph.nodes(data: true).length

    e[u] = length.max_by { |a| a[1] }[1]
  end
  node.nil? ? e : e[node]
end

.edge_dfs(graph, start, orientation = nil) ⇒ Object

Performs edge dfs on the graph Orientation :ignore, directed edges can be travelled in both fashions Orientation reverse, directed edges can be travelled in reverse fashion Orientation :nil, the graph is not meddled with

Examples:

NetworkX.edge_dfs(graph, source, 'ignore')

Parameters:

  • graph (Graph, DiGraph, MultiGraph, MultiDiGraph)

    a graph

  • start (Object)

    node to start dfs from

  • orientation (:ignore, :reverse', nil) (defaults to: nil)

    the orientation of edges of graph



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/networkx/traversals/edge_dfs.rb', line 51

def self.edge_dfs(graph, start, orientation = nil)
  case orientation
  when :reverse
    graph = graph.reverse if graph.instance_of?(::NetworkX::DiGraph) || graph.instance_of?(::NetworkX::MultiDiGraph)
  when :ignore
    graph = graph.to_undirected if graph.instance_of?(::NetworkX::DiGraph)
    graph = graph.to_multigraph if graph.instance_of?(::NetworkX::MultiDiGraph)
  end

  visited_edges = []
  visited_nodes = []
  stack = [start]
  current_edges = {}

  e = Enumerator.new do |yield_var|
    until stack.empty?
      current = stack.last
      unless visited_nodes.include?(current)
        current_edges[current] = out_edges(graph, current)
        visited_nodes << current
      end

      edge = current_edges[current].shift
      if edge.nil?
        stack.pop
      else
        unless visited_edges.include?(edge_id(graph, edge))
          visited_edges << edge_id(graph, edge)
          stack << edge[1]
          yield_var.yield edge
        end
      end
    end
  end
  e.take(graph.number_of_edges)
end

.edge_id(graph, edge) ⇒ Object

Helper function of edge_dfs



31
32
33
34
35
36
# File 'lib/networkx/traversals/edge_dfs.rb', line 31

def self.edge_id(graph, edge)
  return edge if graph.directed?
  return Set.new([edge, (edge[0..1].reverse << edge[2])]) if graph.multigraph?

  Set.new([edge, edge.reverse])
end

.edges_cross_nodes(g1, g2) ⇒ Object

Returns the product of edges with edges



62
63
64
65
66
67
68
69
70
# File 'lib/networkx/operators/product.rb', line 62

def self.edges_cross_nodes(g1, g2)
  result = []
  edges_in_array(g1).each do |u, v, d|
    g2.nodes(data: true).each_key do |x|
      result << [[u, x], [v, x], d]
    end
  end
  result
end

.edges_cross_nodes_and_nodes(g1, g2) ⇒ Object

Returns the product of edges with pairs of nodes



84
85
86
87
88
89
90
91
92
93
94
# File 'lib/networkx/operators/product.rb', line 84

def self.edges_cross_nodes_and_nodes(g1, g2)
  result = []
  edges_in_array(g1).each do |u, v, d|
    g2.nodes(data: true).each_key do |x|
      g2.nodes(data: true).each_key do |y|
        result << [[u, x], [v, y], d]
      end
    end
  end
  result
end

.edges_in_array(graph) ⇒ Object

Returns the edges of the graph in an array



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/networkx/operators/product.rb', line 3

def self.edges_in_array(graph)
  edge_array = []
  if graph.multigraph?
    graph.adj.each do |u, u_edges|
      u_edges.each do |v, uv_edges|
        uv_edges.each do |_k, attrs|
          edge_array << [u, v, attrs]
        end
      end
    end
  else
    graph.adj.each do |u, u_edges|
      u_edges.each do |v, attrs|
        edge_array << [u, v, attrs]
      end
    end
  end
  edge_array
end

.edmondskarp(graph, source, target, residual = nil, cutoff = nil) ⇒ DiGraph

Computes max flow using edmonds karp algorithm

Parameters:

  • graph (Graph, DiGraph)

    a graph

  • source (Object)

    source node

  • target (Object)

    target node

  • residual (DiGraph, nil) (defaults to: nil)

    residual graph

  • cutoff (Numeric) (defaults to: nil)

    cutoff for the algorithm

Returns:

  • (DiGraph)

    a residual graph containing the flow values



112
113
114
# File 'lib/networkx/flow/edmondskarp.rb', line 112

def self.edmondskarp(graph, source, target, residual = nil, cutoff = nil)
  edmondskarp_impl(graph, source, target, residual, cutoff)
end

.edmondskarp_core(residual, source, target, cutoff) ⇒ Object

Core helper function for the EdmondsKarp algorithm



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/networkx/flow/edmondskarp.rb', line 61

def self.edmondskarp_core(residual, source, target, cutoff)
  inf = residual.graph[:inf]
  flow_val = 0
  while flow_val < cutoff
    v, pred, succ = bidirectional_bfs(residual, source, target)
    break if pred.nil?

    path = [v]
    u = v
    while u != source
      u = pred[u]
      path << u
    end
    path.reverse!
    u = v
    while u != target
      u = succ[u]
      path << u
    end
    flow_val += augment(residual, inf, path)
  end
  flow_val
end

.edmondskarp_impl(graph, source, target, residual, cutoff) ⇒ Object

Helper function for the edmondskarp function

Raises:

  • (ArgumentError)


86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/networkx/flow/edmondskarp.rb', line 86

def self.edmondskarp_impl(graph, source, target, residual, cutoff)
  raise ArgumentError, 'Source not in graph!' unless graph.nodes(data: true).has_key?(source)
  raise ArgumentError, 'Target not in graph!' unless graph.nodes(data: true).has_key?(target)
  raise ArgumentError, 'Source and target are same node!' if source == target

  res_graph = residual.nil? ? build_residual_network(graph) : residual.clone
  res_graph.adj.each do |u, u_edges|
    u_edges.each do |v, _attrs|
      res_graph.adj[u][v][:flow] = 0
      res_graph.pred[v][u][:flow] = 0
    end
  end
  cutoff = Float::INFINITY if cutoff.nil?
  res_graph.graph[:flow_value] = edmondskarp_core(res_graph, source, target, cutoff)
  res_graph
end

.find_cliques(graph) ⇒ Array<Array<Object>>

Returns all cliques in the graph

Parameters:

Returns:

  • (Array<Array<Object>>)

    Arrays of nodes in the cliques



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/networkx/auxillary_functions/cliques.rb', line 7

def self.find_cliques(graph)
  return nil if graph.nodes(data: true).empty?

  q = [nil]
  adj = {}
  graph.nodes(data: true).each_key { |u| adj[u] = [] }
  graph.adj.each { |u, u_edges| u_edges.each_key { |v| adj[u] << v if u != v } }

  subg = graph.nodes(data: true).keys
  cand = graph.nodes(data: true).keys
  u = subg.max { |n1, n2| (cand & adj[n1]).length <=> (cand & adj[n2]).length }
  ext_u = cand - adj[u]
  stack = []
  cliques = []
  begin
    loop do
      if ext_u.empty?
        q.pop
        subg, cand, ext_u = stack.pop
      else
        q_elem = ext_u.pop
        cand.delete(q_elem)
        q[-1] = q_elem
        adj_q = adj[q_elem]
        subg_q = subg & adj_q
        if subg_q.empty?
          cliques << q[0..(q.length - 1)]
        else
          cand_q = cand & adj_q
          unless cand_q.empty?
            stack << [subg, cand, ext_u]
            q << nil
            subg = subg_q
            cand = cand_q
            u = subg.max { |n1, n2| (cand & adj[n1]).length <=> (cand & adj[n2]).length }
            ext_u = cand - adj[u]
          end
        end
      end
    end
  rescue NoMethodError
    cliques
  end
end

.find_cycle(graph, node) ⇒ Array<Array<Object>>

Returns the cycle containing the given node

Parameters:

  • graph (Graph, DiGraph)

    a graph

  • node (Object)

    node to be included in the cycle

Returns:

  • (Array<Array<Object>>)

    Arrays of nodes in the cycle

Raises:

  • (ArgumentError)


53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/networkx/auxillary_functions/cycles.rb', line 53

def self.find_cycle(graph, node)
  explored = Set.new
  cycle = []
  final_node = nil
  unless explored.include?(node)
    edges = []
    seen = [node]
    active_nodes = [node]
    previous_head = nil

    edge_dfs(graph, node).each do |edge|
      tail, head = edge
      next if explored.include?(head)

      if !previous_head.nil? && tail != previous_head
        loop do
          popped_edge = edges.pop
          if popped_edge.nil?
            edges = []
            active_nodes = [tail]
            break
          else
            popped_head = popped_edge[1]
            active_nodes.delete(popped_head)
          end

          unless edges.empty?
            last_head = edges[-1][1]
            break if tail == last_head
          end
        end
      end
      edges << edge

      if active_nodes.include?(head)
        cycle += edges
        final_node = head
        break
      else
        seen << head
        active_nodes << head
        previous_head = head
      end
    end
    cycle.each_with_index { |edge, i| return cycle[i..(cycle.length - 1)] if final_node == edge[0] }
  end
  raise ArgumentError, 'No cycle found!' if cycle.empty?
end

.floyd_warshall(graph) ⇒ Hash{ Object => { Object => { Numeric }}}

Returns the all pair distance between all the nodes

Parameters:

Returns:

  • (Hash{ Object => { Object => { Numeric }}})

    a hash containing distances b/w all pairs of nodes



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/networkx/shortest_path/dense.rb', line 8

def self.floyd_warshall(graph)
  a, index = to_matrix(graph, Float::INFINITY, 'min')
  nodelen = graph.nodes(data: true).length
  (0..(nodelen - 1)).each { |i| a[i, i] = 0 }
  (0..(nodelen - 1)).each do |k|
    (0..(nodelen - 1)).each do |i|
      (0..(nodelen - 1)).each do |j|
        a[i, j] = [a[i, j], a[i, k] + a[k, j]].min
      end
    end
  end

  as_hash = {}
  (0..(nodelen - 1)).each do |i|
    (0..(nodelen - 1)).each do |j|
      as_hash[index[i]] = {} unless as_hash.has_key?(index[i])
      as_hash[index[i]][index[j]] = a[i, j]
    end
  end
  as_hash
end

.gap_heuristic(height, levels, residual_nodes) ⇒ Object

Helper function for applying gap heuristic



168
169
170
171
172
173
174
175
176
177
178
# File 'lib/networkx/flow/preflowpush.rb', line 168

def self.gap_heuristic(height, levels, residual_nodes)
  ((height + 1)..(max_height)).each do |idx|
    level = levels[idx]
    level.active.each { |u| residual_nodes[u][:height] = n + 1 }
    level.inactive.each { |u| residual_nodes[u][:height] = n + 1 }
    levels[n + 1].active.merge!(level.active)
    level.active.clear
    levels[n + 1].inactive.merge!(level.inactive)
    level.inactive.clear
  end
end

.generate_unique_nodeObject

Returns a label for unique node



3
4
5
# File 'lib/networkx/flow/capacityscaling.rb', line 3

def self.generate_unique_node
  SecureRandom.uuid
end

.get_edges(graph) ⇒ Object

Returns the edges of the graph in an array



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/networkx/operators/binary.rb', line 3

def self.get_edges(graph)
  edges = []
  if graph.is_a?(MultiGraph)
    graph.adj.each do |u, v_keys|
      v_keys.each do |v, key_attrs|
        next if u > v

        key_attrs.each do |_key, attributes|
          edges << [u, v, attributes]
        end
      end
    end
  else
    graph.adj.each do |u, u_attrs|
      u_attrs.each do |v, uv_attrs|
        edges << [u, v, uv_attrs]
      end
    end
  end
  edges
end

.get_edges_weights(graph) ⇒ Object

Helper function for the minimum spanning tree



4
5
6
7
8
9
10
11
12
# File 'lib/networkx/auxillary_functions/mst.rb', line 4

def self.get_edges_weights(graph)
  edges = []
  graph.adj.each do |u, u_edges|
    u_edges.each do |v, uv_attrs|
      edges << [[u, v], uv_attrs[:weight] || Float::INFINITY]
    end
  end
  edges
end

.get_sources(graph) ⇒ Object

Helper function to get sources



380
381
382
# File 'lib/networkx/shortest_path/weighted.rb', line 380

def self.get_sources(graph)
  graph.nodes(data: true).collect { |k, _v| k }
end

.get_weight(graph) ⇒ Object

Helper function to extract weight from a adjecency hash



3
4
5
6
7
8
9
# File 'lib/networkx/shortest_path/weighted.rb', line 3

def self.get_weight(graph)
  lambda do |_, _, attrs|
    return attrs[:weight] || 1 unless graph.multigraph?

    attrs.group_by { |_k, vals| vals[:weight] || 1 }.keys.max
  end
end

.global_relabel(from_sink, source, target, residual_nodes, num, levels, residual_pred) ⇒ Object

Helper function for global relabel heuristic



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/networkx/flow/preflowpush.rb', line 181

def self.global_relabel(from_sink, source, target, residual_nodes, num, levels, residual_pred)
  src = from_sink ? target : source
  heights = reverse_bfs(src, residual_pred)
  heights.delete(target) unless from_sink
  max_height = heights.values.max
  if from_sink
    residual_nodes.each { |u, attr| heights[u] = num + 1 if !heights.has_key?(u) && attr[:height] < num }
  else
    heights.each_key { |u| heights[u] += num }
    max_height += num
  end
  heights.delete(src)
  heights.each do |u, new_height|
    old_height = residual_nodes[u][:height]
    next unless new_height != old_height

    if levels[old_height].active.include?(u)
      levels[old_height].active.delete(u)
      levels[new_height].active.add(u)
    else
      levels[old_height].inactive.delete(u)
      levels[new_height].inactive.add(u)
    end
    residual_nodes[u][:height] = new_height
  end
  max_height
end

.graph_to_csv(graph, filename = 'graph.csv') ⇒ Object

Saves the graph in a csv file

Parameters:



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/networkx/converters/to_csv.rb', line 6

def self.graph_to_csv(graph, filename = 'graph.csv')
  CSV.open(filename, 'wb') do |csv|
    csv << [graph.class.name]
    csv << ['graph_values']
    csv << graph.graph.keys
    csv << graph.graph.values
    csv << ['graph_nodes']
    graph.nodes(data: true).each do |u, attrs|
      node_attrs = [u]
      attrs.each do |k, v|
        node_attrs << k
        node_attrs << v
      end
      csv << node_attrs
    end
    csv << ['graph_edges']
    graph.adj.each do |u, u_edges|
      u_edges.each do |v, uv_attrs|
        if graph.multigraph?
          uv_attrs.each do |key, attrs|
            node_attrs = [u, v, key]
            attrs.each do |k, k_attrs|
              node_attrs << k
              node_attrs << k_attrs
            end
            csv << node_attrs
          end
        else
          node_attrs = [u, v]
          uv_attrs.each do |k, vals|
            node_attrs << k
            node_attrs << vals
          end
          csv << node_attrs
        end
      end
    end
  end
end

.graph_to_json(graph) ⇒ JSON

Returns a JSON object of the given graph

Parameters:

Returns:

  • (JSON)

    json encoded graph



7
8
9
10
11
12
13
14
# File 'lib/networkx/converters/to_json.rb', line 7

def self.graph_to_json(graph)
  json_hash = {}
  json_hash[:class] = graph.class.name
  json_hash[:graph] = graph.graph
  json_hash[:nodes] = graph.nodes
  json_hash[:adj] = graph.adj
  json_hash.to_json
end

.grid_2d_graph(m, n, periodic: false, create_using: NetworkX::Graph) ⇒ Object

Parameters:

  • m (Integer)

    the number of rows

  • n (Integer)

    the number of columns

  • create_using (Class) (defaults to: NetworkX::Graph)

    graph class. this is optional. default is NetworkX::Graph.



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/networkx/others/grid_2d_graph.rb', line 12

def self.grid_2d_graph(m, n, periodic: false, create_using: NetworkX::Graph)
  warn('sorry, periodic is not done yet') if periodic

  m.is_a?(Integer) or raise(ArgumentError, "[NetworkX] argument m: Integer, not #{m.class}")
  n.is_a?(Integer) or raise(ArgumentError, "[NetworkX] argument n: Integer, not #{n.class}")
  create_using.is_a?(Class) \
    or raise(ArgumentError, "[NetworkX] argument create_using: `Graph` class or children, not #{create_using.class}")

  g = create_using.new

  a = []
  m.times { |i| n.times { |j| a << [i, j] } }
  g.add_nodes_from(a)

  e1 = []
  (m - 1).times { |i| n.times { |j| e1 << [[i, j], [i + 1, j]] } }
  g.add_edges_from(e1)

  e2 = []
  m.times { |i| (n - 1).times { |j| e2 << [[i, j], [i, j + 1]] } }
  g.add_edges_from(e2)

  g.add_edges_from(g.edges.map { |i, j| [j, i] }) if g.directed?

  g
end

.hash_product(hash1, hash2) ⇒ Object

Returns the hash product of two hashes



24
25
26
# File 'lib/networkx/operators/product.rb', line 24

def self.hash_product(hash1, hash2)
  (hash1.keys | hash2.keys).to_h { |n| [n, [hash1[n], hash2[n]]] }
end

.help_bellman_ford(graph, sources, weight, pred = nil, paths = nil, dist = nil, cutoff = nil, target = nil) ⇒ Object

Helper function for bellman ford



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
# File 'lib/networkx/shortest_path/weighted.rb', line 220

def self.help_bellman_ford(graph, sources, weight, pred = nil, paths = nil, dist = nil, cutoff = nil, target = nil)
  pred = sources.product([[]]).to_h if pred.nil?
  dist = sources.product([0]).to_h if dist.nil?

  inf, n, count, q, in_q = Float::INFINITY, graph.nodes(data: true).length, {}, sources.clone, Set.new(sources)
  until q.empty?
    u = q.shift
    in_q.delete(u)
    skip = false
    pred[u].each { |k| skip = true if in_q.include?(k) }
    next if skip

    dist_u = dist[u]
    graph.adj[u].each do |v, e|
      dist_v = dist_u + weight.call(u, v, e)
      next if !cutoff.nil? && dist_v > cutoff
      next if !target.nil? && dist_v > (dist[target] || inf)

      if dist_v < (dist[v] || inf)
        unless in_q.include?(v)
          q << v
          in_q.add(v)
          count_v = (count[v] || 0) + 1
          raise ArgumentError, 'Negative edge cycle detected!' if count_v == n

          count[v] = count_v
        end
        dist[v] = dist_v
        pred[v] = [u]
      elsif dist.has_key?(v) && dist_v == dist[v]
        pred[v] << u
      end
    end
  end
  unless paths.nil?
    dsts = pred
    dsts.each_key do |dst|
      path, cur = [dst], dst
      until pred[cur][0].nil?
        cur = pred[cur][0]
        path << cur
      end
      path.reverse
      paths[dst] = path
    end
  end
  dist
end

.help_dijkstra(graph, source, weight, pred = nil, paths = nil, cutoff = nil, target = nil) ⇒ Object

Helper function for single source dijkstra



53
54
55
# File 'lib/networkx/shortest_path/weighted.rb', line 53

def self.help_dijkstra(graph, source, weight, pred = nil, paths = nil, cutoff = nil, target = nil)
  help_multisource_dijkstra(graph, [source], weight, pred, paths, cutoff, target)
end

.help_multisource_dijkstra(graph, sources, weight, pred = nil, paths = nil, cutoff = nil, target = nil) ⇒ Object

Helper function for multisource dijkstra



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/networkx/shortest_path/weighted.rb', line 12

def self.help_multisource_dijkstra(graph, sources, weight, pred = nil, paths = nil, cutoff = nil, target = nil)
  count = ->(i) { i + 1 }
  i = -1
  dist = {}
  seen = {}
  fringe = Heap.new { |x, y| x[0] < y[0] || (x[0] == y[0] && x[1] < y[1]) }
  sources.each do |s|
    seen[s] = 0
    fringe << [0, count.call(i), s]
  end

  until fringe.empty?
    d, _, v = fringe.pop
    next if dist.has_key?(v)

    dist[v] = d
    break if v == target

    graph.adj[v].each do |u, attrs|
      cost = weight.call(v, u, attrs)
      next if cost.nil?

      vu_dist = dist[v] + cost
      next if !cutoff.nil? && vu_dist > cutoff

      if dist.has_key?(u)
        raise ValueError, 'Contradictory weights found!' if vu_dist < dist[u]
      elsif !seen.has_key?(u) || vu_dist < seen[u]
        seen[u] = vu_dist
        fringe << [vu_dist, count.call(i), u]
        paths[u] = paths[v] + [u] unless paths.nil?
        pred[u] = [v] unless pred.nil?
      elsif vu_dist == seen[u]
        pred[u] << v unless pred.nil?
      end
    end
  end
  dist
end

.help_single_shortest_path(adj, firstlevel, paths, cutoff) ⇒ Object

Helper function for finding single source shortest path



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/networkx/shortest_path/unweighted.rb', line 53

def self.help_single_shortest_path(adj, firstlevel, paths, cutoff)
  level = 0
  nextlevel = firstlevel
  while !nextlevel.empty? && cutoff > level
    thislevel = nextlevel
    nextlevel = {}
    thislevel.each_key do |u|
      adj[u].each_key do |v|
        unless paths.has_key?(v)
          paths[v] = paths[u] + [v]
          nextlevel[v] = 1
        end
      end
    end
    level += 1
  end
  paths
end

.help_single_shortest_path_length(adj, firstlevel, cutoff) ⇒ Object

Helper function for single source shortest path length



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/networkx/shortest_path/unweighted.rb', line 3

def self.help_single_shortest_path_length(adj, firstlevel, cutoff)
  Enumerator.new do |e|
    seen = {}
    level = 0
    nextlevel = firstlevel

    while !nextlevel.empty? && cutoff >= level
      thislevel = nextlevel
      nextlevel = {}
      thislevel.each do |u, _attrs|
        next if seen.has_key?(u)

        seen[u] = level
        nextlevel.merge!(adj[u])
        e.yield [u, level]
      end
      level += 1
    end
    seen.clear
  end
end

.hits(graph, max_iter = 100, tol = 1e-8, nstart) ⇒ Array<Numeric, Numeric>

Computes hits and authority scores for all the graphs

Parameters:

  • graph (Graph, DiGraph)

    a graph

  • max_iter (Integer) (defaults to: 100)

    max iterations to run the hits algorithm

  • tol (Numeric) (defaults to: 1e-8)

    tolerences to cut off the loop

  • nstart (Array<Numeric>)

    starting hub values for the nodes

Returns:

  • (Array<Numeric, Numeric>)

    hits and authority scores



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/networkx/link_analysis/hits.rb', line 10

def self.hits(graph, max_iter = 100, tol = 1e-8, nstart)
  return [{}, {}] if graph.nodes(data: true).empty?

  h = nstart
  sum = h.values.sum
  h.each_key { |k| h[k] /= (sum * 1.0) }
  i = 0
  a = {}

  loop do
    hlast = Marshal.load(Marshal.dump(h))
    h, a = {}, {}
    hlast.each do |k, _v|
      h[k] = 0
      a[k] = 0
    end
    h.each_key { |k| graph.adj[k].each { |nbr, attrs| a[k] += hlast[nbr] * (attrs[:weight] || 1) } }
    h.each_key { |k| graph.adj[k].each { |nbr, attrs| h[k] += a[nbr] * (attrs[:weight] || 1) } }
    smax = h.values.max
    h.each_key { |k| h[k] /= smax }
    smax = a.values.max
    a.each_key { |k| a[k] /= smax }
    break if h.keys.map { |k| (h[k] - hlast[k]).abs }.sum < tol
    raise ArgumentError, 'Power Iteration failed to converge!' if i > max_iter

    i += 1
  end
  [h, a]
end

.hub_matrix(graph) ⇒ Matrix

Computes hub matrix for the graph

Parameters:

Returns:

  • (Matrix)

    hub matrix for the graph



55
56
57
58
# File 'lib/networkx/link_analysis/hits.rb', line 55

def self.hub_matrix(graph)
  matrix, = to_matrix(graph, 0)
  matrix * matrix.transpose
end

.info(graph) ⇒ Object



4
5
6
7
8
9
10
# File 'lib/networkx/others/info.rb', line 4

def self.info(graph)
  info = ''
  info << "Type: #{graph.class}\n"
  info << "Number of nodes: #{graph.number_of_nodes}\n"
  info << "Number of edges: #{graph.number_of_edges}\n"
  info
end

.init_product_graph(g1, g2) ⇒ Object

Initializes the product graph

Raises:

  • (ArgumentError)


97
98
99
100
101
102
103
104
105
106
107
# File 'lib/networkx/operators/product.rb', line 97

def self.init_product_graph(g1, g2)
  raise ArgumentError, 'Arguments must be both directed or undirected!' unless g1.directed? == g2.directed?

  g = if g1.multigraph? || g2.multigraph?
        NetworkX::MultiGraph.new
      else
        NetworkX::Graph.new
      end
  g = g.to_directed if g.directed?
  g
end

.intersection(g1, g2) ⇒ Graph, ...

Performs the intersection of two graphs

Parameters:

Returns:

Raises:

  • (ArgumentError)


59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/networkx/operators/binary.rb', line 59

def self.intersection(g1, g2)
  result = g1.class.new

  raise ArgumentError, 'Arguments must be both Graphs or MultiGraphs!' unless g1.multigraph? == g2.multigraph?

  unless (g1.nodes(data: true).keys - g2.nodes(data: true).keys).empty?
    raise ArgumentError, 'Node sets must be equal!'
  end

  g1.nodes(data: true).each { |u, attrs| result.add_node(u, **attrs) }

  g1, g2 = g2, g1 if g1.number_of_edges > g2.number_of_edges
  g1.adj.each do |u, u_edges|
    u_edges.each do |v, uv_attrs|
      if g1.multigraph?
        next if u > v && g1.instance_of?(MultiGraph)

        uv_attrs.each do |k, attrs|
          result.add_edge(u, v, **attrs) if g2.edge?(u, v, k)
        end
      elsif g2.edge?(u, v)
        result.add_edge(u, v, **uv_attrs)
      end
    end
  end
  result
end

.intersection_all(graphs) ⇒ Graph, ...

Performs the intersection of many graphs

Parameters:

Returns:

Raises:

  • (ArgumentError)


39
40
41
42
43
44
45
46
47
48
# File 'lib/networkx/operators/all.rb', line 39

def self.intersection_all(graphs)
  raise ArgumentError, 'Argument array is empty' if graphs.empty?

  result = graphs.shift

  graphs.each do |graph|
    result = NetworkX.intersection(result, graph)
  end
  result
end

.johnson(graph) ⇒ Array<Object, Hash { Object => Array<Object> }] shortest paths between all pairs of nodes

Returns shortest path between all pairs of nodes

Parameters:

Returns:

  • (Array<Object, Hash { Object => Array<Object> }] shortest paths between all pairs of nodes)

    Array<Object, Hash { Object => Array<Object> }] shortest paths between all pairs of nodes



405
406
407
408
409
410
411
412
413
414
415
416
# File 'lib/networkx/shortest_path/weighted.rb', line 405

def self.johnson(graph)
  dist, pred = {}, {}
  sources = get_sources(graph)
  graph.nodes(data: true).each_key do |n|
    dist[n], pred[n] = 0, []
  end
  weight = get_weight(graph)
  dist_bellman = help_bellman_ford(graph, sources, weight, pred, nil, dist)
  new_weight = ->(u, v, d) { weight.call(u, v, d) + dist_bellman[u] - dist_bellman[v] }
  dist_path = dist_path_lambda(graph, new_weight)
  set_path_lengths_johnson(graph, dist_path, new_weight)
end

.json_to_graph(json_str) ⇒ Graph, ...

Returns a graph from the json encoded graph

Parameters:

  • json_str (JSON)

    json encoded string

Returns:



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/networkx/converters/to_json.rb', line 21

def self.json_to_graph(json_str)
  graph_hash = JSON.parse(json_str)
  case json_str['class']
  when 'NetworkX::Graph'
    graph = NetworkX::Graph.new(graph_hash.graph)
  when 'NetworkX::MultiGraph'
    graph = NetworkX::MultiGraph.new(graph_hash.graph)
  when 'NetworkX::DiGraph'
    graph = NetworkX::DiGraph.new(graph_hash.graph)
  when 'NetworkX::MultiDiGraph'
    graph = NetworkX::MultiDiGraph.new(graph_hash.graph)
  end
  graph.adj = graph_hash['adj']
  graph.nodes = graph_hash['nodes']
  graph
end

.lexicographic_product(g1, g2) ⇒ Graph, ...

Returns the lexicographic product of two graphs

Parameters:

Returns:



143
144
145
146
147
148
149
# File 'lib/networkx/operators/product.rb', line 143

def self.lexicographic_product(g1, g2)
  g = init_product_graph(g1, g2)
  g.add_nodes(node_product(g1, g2))
  g.add_edges(edges_cross_nodes_and_nodes(g1, g2))
  g.add_edges(nodes_cross_edges(g1, g2))
  g
end

.maximal_independent_set(graph, nodes) ⇒ Numeric

Returns the maximal independent set of a graph

Parameters:

Returns:

  • (Numeric)

    radius of the graph



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/networkx/auxillary_functions/mis.rb', line 8

def self.maximal_independent_set(graph, nodes)
  if (graph.nodes(data: true).keys - nodes).empty?
    raise 'The array containing the nodes should be a subset of the graph!'
  end

  neighbours = []
  nodes.each { |u| graph.adj[u].each { |v, _| neighbours |= [v] } }
  raise 'Nodes is not an independent set of graph!' if (neighbours - nodes).empty?

  available_nodes = graph.nodes(data: true).keys - (neighbours | nodes)
  until available_nodes.empty?
    node = available_nodes.sample
    nodes << node
    available_nodes -= (graph.adj[node].keys + [node])
  end
  nodes
end

.minimum_spanning_tree(graph) ⇒ DiGraph, Graph

Returns the minimum spanning tree of a graph

Parameters:

Returns:



19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/networkx/auxillary_functions/mst.rb', line 19

def self.minimum_spanning_tree(graph)
  mst = Marshal.load(Marshal.dump(graph))
  mst.clear
  edges = get_edges_weights(graph).sort_by { |a| a[1] }
  union_find = UnionFind.new(graph.nodes(data: true).keys)
  while edges.any? && mst.nodes(data: true).length <= graph.nodes(data: true).length
    edge = edges.shift
    unless union_find.connected?(edge[0][0], edge[0][1])
      union_find.union(edge[0][0], edge[0][1])
      mst.add_edge(edge[0][0], edge[0][1], **graph.adj[edge[0][0]][edge[0][1]])
    end
  end
  mst
end

.multisource_dijkstra(graph, sources, target = nil, cutoff = nil) ⇒ Numeric, Array<Object>

Computes shortest paths and path lengths to a target from one of the nodes

Parameters:

  • graph (Graph, DiGraph, MultiGraph, MultiDiGraph)

    a graph

  • sources (Array<Object>)

    Array of sources

  • target (Object, nil) (defaults to: nil)

    target node for the dijkstra algorithm

  • cutoff (Numeric, nil) (defaults to: nil)

    cutoff for the dijkstra algorithm

Returns:

  • (Numeric, Array<Object>)

    path lengths for all nodes

Raises:

  • (ValueError)


65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/networkx/shortest_path/weighted.rb', line 65

def self.multisource_dijkstra(graph, sources, target = nil, cutoff = nil)
  raise ValueError, 'Sources cannot be empty' if sources.empty?
  return [0, [target]] if sources.include?(target)

  paths = {}
  weight = get_weight(graph)
  sources.each { |source| paths[source] = [source] }
  dist = help_multisource_dijkstra(graph, sources, weight, nil, paths, cutoff, target)
  return [dist, paths] if target.nil?
  raise KeyError, "No path to #{target}!" unless dist.has_key?(target)

  [dist[target], paths[target]]
end

.multisource_dijkstra_path(graph, sources, cutoff = nil) ⇒ Hash{ Object => Array<Object> }

Computes shortest paths to any from the given nodes

Parameters:

Returns:

  • (Hash{ Object => Array<Object> })

    paths for any nodes from given nodes



100
101
102
103
# File 'lib/networkx/shortest_path/weighted.rb', line 100

def self.multisource_dijkstra_path(graph, sources, cutoff = nil)
  _, path = multisource_dijkstra(graph, sources, nil, cutoff)
  path
end

.multisource_dijkstra_path_length(graph, sources, cutoff = nil) ⇒ Hash{ Object => Numeric }

Computes shortest path lengths to any from the given nodes

Parameters:

Returns:

  • (Hash{ Object => Numeric })

    path lengths for any nodes from given nodes

Raises:

  • (ValueError)


86
87
88
89
90
91
# File 'lib/networkx/shortest_path/weighted.rb', line 86

def self.multisource_dijkstra_path_length(graph, sources, cutoff = nil)
  raise ValueError, 'Sources cannot be empty' if sources.empty?

  weight = get_weight(graph)
  help_multisource_dijkstra(graph, sources, weight, nil, nil, cutoff)
end

.negative_edge_cycle(graph) ⇒ Boolean

Finds if there is a negative edge cycle in the graph

Parameters:

Returns:

  • (Boolean)

    whether there exists a negative cycle in graph



12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/networkx/flow/capacityscaling.rb', line 12

def self.negative_edge_cycle(graph)
  newnode = generate_unique_node
  graph.add_edges(graph.nodes(data: true).keys.map { |n| [newnode, n] })
  begin
    bellmanford_predecesor_distance(graph, newnode)
  rescue ArgumentError
    return true
  ensure
    graph.remove_node(newnode)
  end
  false
end

.node_product(g1, g2) ⇒ Object

Returns the node product of nodes of two graphs



29
30
31
32
33
34
35
36
37
# File 'lib/networkx/operators/product.rb', line 29

def self.node_product(g1, g2)
  n_product = []
  g1.nodes(data: true).each do |k1, attrs1|
    g2.nodes(data: true).each do |k2, attrs2|
      n_product << [[k1, k2], hash_product(attrs1, attrs2)]
    end
  end
  n_product
end

.nodes_cross_edges(g1, g2) ⇒ Object

Returns the product of directed nodes with edges



73
74
75
76
77
78
79
80
81
# File 'lib/networkx/operators/product.rb', line 73

def self.nodes_cross_edges(g1, g2)
  result = []
  g1.nodes(data: true).each_key do |x|
    edges_in_array(g2).each do |u, v, d|
      result << [[x, u], [x, v], d]
    end
  end
  result
end

.number_connected_components(graph) ⇒ Integer Also known as: number_of_connected_components

Returns the number of connected components on graph.

Parameters:

Returns:

  • (Integer)

    the number of connected components on graph



8
9
10
11
12
# File 'lib/networkx/others/number_connected_components.rb', line 8

def self.number_connected_components(graph)
  uf = NetworkX::UnionFind.new(graph.nodes(data: false))
  graph.each_edge { |x, y| uf.unite(x, y) }
  uf.groups.size
end

.number_of_bridges(graph) ⇒ Integer

Returns the number of bridges.

Parameters:

  • graph (Graph)

    Graph

Returns:

  • (Integer)

    the number of bridges



27
28
29
# File 'lib/networkx/others/bridges.rb', line 27

def self.number_of_bridges(graph)
  bridges(graph).size
end

.number_of_cliques(graph, node) ⇒ Numeric

Returns the number of cliques in a graph containing a node

Parameters:

Returns:

  • (Numeric)

    Number of cliques containing the given node



58
59
60
61
# File 'lib/networkx/auxillary_functions/cliques.rb', line 58

def self.number_of_cliques(graph, node)
  cliques = find_cliques(graph)
  cliques.count { |c| c.include?(node) }
end

.out_edges(graph, node) ⇒ Object

Helper function for edge_dfs

Parameters:



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/networkx/traversals/edge_dfs.rb', line 6

def self.out_edges(graph, node)
  edges = []
  visited = {}
  case graph.class.name
  when 'NetworkX::Graph', 'NetworkX::DiGraph'
    graph.adj[node].each do |v, _|
      if graph.instance_of?(::NetworkX::DiGraph) || visited[[v, node]].nil?
        visited[[node, v]] = true
        edges << [node, v]
      end
    end
  else
    graph.adj[node].each do |v, uv_keys|
      uv_keys.each_key do |k|
        if graph.instance_of?(::NetworkX::MultiDiGraph) || visited[[v, node, k]].nil?
          visited[[node, v, k]] = true
          edges << [node, v, k]
        end
      end
    end
  end
  edges
end

.pagerank(graph, alpha: 0.85, personalization: nil, eps: 1e-6, max_iter: 100) ⇒ Hash of Object => Float

Computes pagerank values for the graph

Parameters:

  • graph (Graph)

    a graph

  • alpha (Numeric) (defaults to: 0.85)

    the alpha value to compute the pagerank

  • eps (Numeric) (defaults to: 1e-6)

    tolerence to check for convergence

  • max_iter (Integer) (defaults to: 100)

    max iterations for the pagerank algorithm to run

Returns:

  • (Hash of Object => Float)

    pagerank values of the graph



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/networkx/link_analysis/pagerank.rb', line 10

def self.pagerank(graph, alpha: 0.85, personalization: nil, eps: 1e-6, max_iter: 100)
  n = graph.number_of_nodes

  matrix, index_to_node = NetworkX.to_matrix(graph, 0)

  index_from_node = index_to_node.invert

  probabilities = Array.new(n) do |i|
    total = matrix.row(i).sum
    (matrix.row(i) / total.to_f).to_a
  end

  curr = personalization
  unless curr
    curr = Array.new(n)
    graph.each_node{|node| curr[index_from_node[node]] = 1.0 / n }
  end

  max_iter.times do
    prev = curr.clone

    n.times do |i|
      ip = 0.0
      n.times do |j|
        ip += probabilities[j][i] * prev[j]
      end
      curr[i] = (alpha * ip) + ((1.0 - alpha) / n * 1.0)
    end

    err = (0...n).map{|i| (prev[i] - curr[i]).abs }.sum
    return (0...n).map{|i| [index_to_node[i], curr[i]] }.sort.to_h if err < eps
  end
  warn "pagerank() failed within #{max_iter} iterations. Please inclease max_iter: or loosen eps:"
  (0...n).map{|i| [index_to_node[i], curr[i]] }.sort.to_h
end

.power(graph, pow) ⇒ Graph, ...

Returns the specified power of the graph

Parameters:

Returns:

Raises:

  • (ArgumentError)


173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/networkx/operators/product.rb', line 173

def self.power(graph, pow)
  raise ArgumentError, 'Power must be a positive quantity!' if pow <= 0

  result = NetworkX::Graph.new
  result.add_nodes(graph.nodes(data: true).map { |n, attrs| [n, attrs] })
  graph.nodes(data: true).each do |n, _attrs|
    seen = {}
    level = 1
    next_level = graph.adj[n]
    until next_level.empty?
      this_level = next_level
      next_level = {}
      this_level.each do |v, _attrs|
        next if v == n

        unless seen.has_key?(v)
          seen[v] = level
          next_level.merge!(graph.adj[v])
        end
      end
      break if pow <= level

      level += 1
    end
    result.add_edges(seen.map { |v, _| [n, v] })
  end
  result
end

.predecessor(graph, source, cutoff = nil, return_seen = false) ⇒ Array<Hash{ Object => Array<Object> }, Hash{ Object => Numeric }>, Hash{ Object => Array<Object> }

Computes shortest paths to all nodes from all nodes

Parameters:

  • graph (Graph, DiGraph, MultiGraph, MultiDiGraph)

    a graph

  • source (Object)

    source for which predecessors are needed

  • cutoff (Numeric, nil) (defaults to: nil)

    cutoff for finding predecessor

  • return_seen (Boolean) (defaults to: false)

    if true, returns seen dict

Returns:

  • (Array<Hash{ Object => Array<Object> }, Hash{ Object => Numeric }>, Hash{ Object => Array<Object> })

    predecessors of a given node and/or seen dict

Raises:

  • (ArgumentError)


110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/networkx/shortest_path/unweighted.rb', line 110

def self.predecessor(graph, source, cutoff = nil, return_seen = false)
  raise ArgumentError, 'Source not found in the Graph!' unless graph.node?(source)

  level = 0
  nextlevel = [source]
  seen = {source => level}
  pred = {source => []}
  until nextlevel.empty?
    level += 1
    thislevel = nextlevel
    nextlevel = []
    thislevel.each do |u|
      graph.adj[u].each_key do |v|
        if !seen.has_key?(v)
          pred[v] = [u]
          seen[v] = level
          nextlevel << v
        elsif seen[v] == level
          pred[v] << u
        end
      end
    end
    break if cutoff.nil? && cutoff <= level
  end
  return_seen ? [pred, seen] : pred
end

.preflowpush(graph, source, target, residual = nil, globalrelabel_freq = 1, value_only = false) ⇒ DiGraph

Computes max flow using preflow push algorithm

Parameters:

  • graph (DiGraph)

    a graph

  • source (Object)

    source node

  • target (Object)

    target node

  • residual (DiGraph, nil) (defaults to: nil)

    residual graph

  • globalrelabel_freq (Numeric) (defaults to: 1)

    global relabel freq

  • value_only (Boolean) (defaults to: false)

    if false, compute maximum flow else maximum preflow

Returns:

  • (DiGraph)

    a residual graph containing the flow values



246
247
248
# File 'lib/networkx/flow/preflowpush.rb', line 246

def self.preflowpush(graph, source, target, residual = nil, globalrelabel_freq = 1, value_only = false)
  preflowpush_impl(graph, source, target, residual, globalrelabel_freq, value_only)
end

.preflowpush_impl(graph, source, target, residual, globalrelabel_freq, value_only) ⇒ Object

Helper function to apply the preflow push algorithm

Raises:

  • (ArgumentError)


14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/networkx/flow/preflowpush.rb', line 14

def self.preflowpush_impl(graph, source, target, residual, globalrelabel_freq, value_only)
  raise ArgumentError, 'Source not in graph!' unless graph.nodes(data: true).has_key?(source)
  raise ArgumentError, 'Target not in graph!' unless graph.nodes(data: true).has_key?(target)
  raise ArgumentError, 'Source and Target are same!' if source == target

  globalrelabel_freq = 0 if globalrelabel_freq.nil?
  raise ArgumentError, 'Global Relabel Freq must be nonnegative!' if globalrelabel_freq.negative?

  r_network = residual.nil? ? build_residual_network(graph) : residual
  detect_unboundedness(r_network, source, target)

  residual_nodes = r_network.nodes
  residual_adj = r_network.adj
  residual_pred = r_network.pred

  residual_nodes.each do |u, u_attrs|
    u_attrs[:excess] = 0
    residual_adj[u].each { |_v, attrs| attrs[:flow] = 0 }
  end

  heights = reverse_bfs(target, residual_pred)

  unless heights.has_key?(source)
    r_network.graph[:flow_value] = 0
    return r_network
  end

  n = r_network.nodes(data: true).length
  max_height = heights.map { |u, h| u == source ? -1 : h }.max
  heights[source] = n

  grt = GlobalRelabelThreshold.new(n, r_network.size, globalrelabel_freq)

  residual_nodes.each do |u, u_attrs|
    u_attrs[:height] = heights.has_key?(u) ? heights[u] : (n + 1)
    u_attrs[:curr_edge] = CurrentEdge.new(residual_adj[u])
  end

  residual_adj[source].each do |u, attr|
    flow = attr[:capacity]
    push(source, u, flow, residual_nodes, residual_adj) if flow.positive?
  end

  levels = (0..((2 * n) - 1)).map { |_| Level.new }
  residual_nodes.each do |u, attr|
    if u != source && u != target
      level = levels[attr[:height]]
      (residual_nodes[u][:excess]).positive? ? level.active.add(u) : level.inactive.add(u)
    end
  end

  height = max_height
  while height.positive?
    loop do
      level = levels[height]
      if level.active.empty?
        height -= 1
        break
      end
      old_height = height
      old_level = level
      u = arbitrary_element(level.active)
      height = discharge(u, true, residual_nodes, residual_adj, height, levels, grt, source, target)
      if grt.reached?
        height = global_relabel(true, source, target, residual_nodes, n, levels, residual_pred)
        max_height = height
        grt.clear_work
      elsif old_level.active.empty? && old_level.inactive.empty?
        gap_heuristic(old_height, levels, residual_nodes)
        height = old_height - 1
        max_height = height
      else
        max_height = [max_height, height].max
      end
    end
  end

  if value_only
    r_network.graph[:flow_value] = residual_nodes[target][:excess]
    return r_network
  end

  height = global_relabel(false, source, target, residual_nodes, n, levels, residual_pred)
  grt.clear_work

  while height > n
    loop do
      level = levels[height]
      if level.active.empty?
        height -= 1
        break
      end
      u = arbitrary_element(level.active)
      height = discharge(u, false, residual_nodes, residual_adj, height, levels, grt, source, target)
      if grt.reached?
        height = global_relabel(false, source, target, residual_nodes, n, levels, residual_pred)
        grt.clear_work
      end
    end
  end
  r_network.graph[:flow_value] = residual_nodes[target][:excess]
  r_network
end

.push(node1, node2, flow, residual_nodes, residual_adj) ⇒ Object

Helper function for augmenting flow



210
211
212
213
214
215
# File 'lib/networkx/flow/preflowpush.rb', line 210

def self.push(node1, node2, flow, residual_nodes, residual_adj)
  residual_adj[node1][node2][:flow] += flow
  residual_adj[node2][node1][:flow] -= flow
  residual_nodes[node1][:excess] -= flow
  residual_nodes[node2][:excess] += flow
end

.radius(graph) ⇒ Numeric

Returns the radius of a graph

Parameters:

Returns:

  • (Numeric)

    radius of the graph



34
35
36
# File 'lib/networkx/auxillary_functions/eccentricity.rb', line 34

def self.radius(graph)
  eccentricity(graph).values.min
end

.relabel(node, num, r_adj, r_nodes) ⇒ Object

Helper function to relable a node to create a permissible edge



129
130
131
132
# File 'lib/networkx/flow/preflowpush.rb', line 129

def self.relabel(u_node, grt, r_adj, _r_nodes, _source, _target, _levels)
  grt.add_work(r_adj[u_node].length)
  r_adj[u_node].map { |v, attr| attr[:flow] < (attr[:capacity] + 1) ? _nodes[v][:height] : Float::INFINITY }.min
end

.reverse_bfs(src, residual_pred) ⇒ Object

Helper function for reverse bfs



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/networkx/flow/preflowpush.rb', line 218

def self.reverse_bfs(src, residual_pred)
  heights = {src => 0}
  q = [[src, 0]]

  until q.empty?
    u, height = q.shift
    height += 1
    residual_pred[u].each do |v, attr|
      if !heights.has_key?(v) && attr[:flow] < attr[:capacity]
        heights[v] = height
        q << [v, height]
      end
    end
  end
  heights
end

.set_path_lengths_johnson(graph, dist_path, new_weight) ⇒ Object

Helper function to set path lengths for Johnson algorithm



394
395
396
397
398
# File 'lib/networkx/shortest_path/weighted.rb', line 394

def self.set_path_lengths_johnson(graph, dist_path, new_weight)
  path_lengths = []
  graph.nodes(data: true).each_key { |n| path_lengths << [n, dist_path.call(graph, n, new_weight)] }
  path_lengths
end

.shortest_augmenting_path(graph, source, target, residual = nil, _value_only = false, two_phase = false, cutoff = nil) ⇒ DiGraph

Computes max flow using shortest augmenting path algorithm

Parameters:

  • graph (DiGraph)

    a graph

  • source (Object)

    source node

  • target (Object)

    target node

  • residual (DiGraph, nil) (defaults to: nil)

    residual graph

  • _value_only (Boolean) (defaults to: false)

    if true, compute only the maximum flow value

  • two_phase (Boolean) (defaults to: false)

    if true, two phase variant is used

  • cutoff (Numeric) (defaults to: nil)

    cutoff value for the algorithm

Returns:

  • (DiGraph)

    a residual graph containing the flow values



149
150
151
152
153
# File 'lib/networkx/flow/shortestaugmentingpath.rb', line 149

def self.shortest_augmenting_path(graph, source, target, residual = nil, \
                                  _value_only = false, two_phase = false, cutoff = nil)

  shortest_augmenting_path_impl(graph, source, target, residual, two_phase, cutoff)
end

.shortest_augmenting_path_impl(graph, source, target, residual, two_phase, cutoff) ⇒ Object

Helper function for running the shortest augmenting path algorithm

Raises:

  • (ArgumentError)


3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/networkx/flow/shortestaugmentingpath.rb', line 3

def self.shortest_augmenting_path_impl(graph, source, target, residual, two_phase, cutoff)
  raise ArgumentError, 'Source is not in the graph!' unless graph.nodes(data: true).has_key?(source)
  raise ArgumentError, 'Target is not in the graph!' unless graph.nodes(data: true).has_key?(target)
  raise ArgumentError, 'Source and Target are the same!' if source == target

  residual = residual.nil? ? build_residual_network(graph) : residual
  r_nodes = residual.nodes
  r_pred = residual.pred
  r_adj = residual.adj

  r_adj.each_value do |u_edges|
    u_edges.each_value do |attrs|
      attrs[:flow] = 0
    end
  end

  heights = {target => 0}
  q = [[target, 0]]

  until q.empty?
    u, height = q.shift
    height += 1
    r_pred[u].each do |v, attrs|
      if !heights.has_key?(v) && attrs[:flow] < attrs[:capacity]
        heights[v] = height
        q << [v, height]
      end
    end
  end

  unless heights.has_key?(source)
    residual.graph[:flow_value] = 0
    return residual
  end

  n = graph.nodes(data: true).length
  m = residual.size / 2

  r_nodes.each do |node, attrs|
    attrs[:height] = heights.has_key?(node) ? heights[node] : n
    attrs[:curr_edge] = CurrentEdge.new(r_adj[node])
  end

  counts = [0] * (2 * n - 1)
  r_nodes.each_value { |attrs| counts[attrs[:height]] += 1 }
  inf = graph.graph[:inf]

  cutoff = Float::INFINITY if cutoff.nil?
  flow_value = 0
  path = [source]
  u = source
  d = two_phase ? n : [m**0.5, 2 * (n**(2./ 3))].min.floor
  done = r_nodes[source][:height] >= d

  until done
    height = r_nodes[u][:height]
    curr_edge = r_nodes[u][:curr_edge]

    loop do
      v, attr = curr_edge.get
      if height == r_nodes[v][:height] + 1 && attr[:flow] < attr[:capacity]
        path << v
        u = v
        break
      end
      begin
        curr_edge.move_to_next
      rescue StopIteration
        if counts[height].zero?
          residual.graph[:flow_value] = flow_value
          return residual
        end
        height = relabel(u, n, r_adj, r_nodes)
        if u == source && height >= d
          if two_phase
            done = true
            break
          else
            residual.graph[:flow_value] = flow_value
            return residual
          end
        end
        counts[height] += 1
        r_nodes[u][:height] = height
        unless u == source
          path.pop
          u = path[-1]
          break
        end
      end
    end
    next unless u == target

    flow_value += augment(path, inf, r_adj)
    if flow_value >= cutoff
      residual.graph[:flow_value] = flow_value
      return residual
    end
  end
  flow_value += edmondskarp_core(residual, source, target, cutoff - flow_value)
  residual.graph[:flow_value] = flow_value
  residual
end

.single_source_shortest_path(graph, source, cutoff = nil) ⇒ Array<Object, Array<Object, Array<Object>>>

Computes single source shortest path from a node to every other node

Parameters:

  • graph (Graph, DiGraph, MultiGraph, MultiDiGraph)

    a graph

  • source (Object)

    source from which shortest paths are needed

  • cutoff (Numeric, nil) (defaults to: nil)

    cutoff for the shortest path algorithm

Returns:

  • (Array<Object, Array<Object, Array<Object>>>)

    path lengths for all nodes from all nodes

Raises:

  • (ArgumentError)


79
80
81
82
83
84
85
86
# File 'lib/networkx/shortest_path/unweighted.rb', line 79

def self.single_source_shortest_path(graph, source, cutoff = nil)
  raise ArgumentError, 'Source not found in the Graph!' unless graph.node?(source)

  cutoff = Float::INFINITY if cutoff.nil?
  nextlevel = {source => 1}
  paths = {source => [source]}
  help_single_shortest_path(graph.adj, nextlevel, paths, cutoff)
end

.single_source_shortest_path_length(graph, source, cutoff = nil) ⇒ Array<Object, Numeric>

Computes shortest path values to all nodes from a given node

Parameters:

  • graph (Graph, DiGraph, MultiGraph, MultiDiGraph)

    a graph

  • source (Object)

    source to compute path length from

  • cutoff (Numeric, nil) (defaults to: nil)

    cutoff for the shortest path algorithm

Returns:

  • (Array<Object, Numeric>)

    path lengths for all nodes

Raises:

  • (ArgumentError)


32
33
34
35
36
37
38
# File 'lib/networkx/shortest_path/unweighted.rb', line 32

def self.single_source_shortest_path_length(graph, source, cutoff = nil)
  raise ArgumentError, 'Source not found in the Graph!' unless graph.node?(source)

  cutoff = Float::INFINITY if cutoff.nil?
  nextlevel = {source => 1}
  help_single_shortest_path_length(graph.adj, nextlevel, cutoff).take(graph.nodes(data: true).length)
end

.singlesource_bellmanford(graph, source, target = nil, cutoff = nil) ⇒ Object

Raises:

  • (ArgumentError)


290
291
292
293
294
295
296
297
298
299
300
# File 'lib/networkx/shortest_path/weighted.rb', line 290

def self.singlesource_bellmanford(graph, source, target = nil, cutoff = nil)
  return [0, [source]] if source == target

  weight = get_weight(graph)
  paths = {source => [source]}
  dist = help_bellman_ford(graph, [source], weight, nil, paths, nil, cutoff, target)
  return [dist, paths] if target.nil?
  raise ArgumentError, 'Node not reachable!' unless dist.has_key?(target)

  [dist[target], paths[target]]
end

.singlesource_bellmanford_path(graph, source, cutoff = nil) ⇒ Hash{ Object => Array<Object> }

Shortest path from source to all nodes using Bellman Ford algorithm

Parameters:

Returns:

  • (Hash{ Object => Array<Object> })

    path from source to all nodes



338
339
340
341
# File 'lib/networkx/shortest_path/weighted.rb', line 338

def self.singlesource_bellmanford_path(graph, source, cutoff = nil)
  _, path = singlesource_bellmanford(graph, source, cutoff)
  path
end

.singlesource_bellmanford_path_length(graph, source, cutoff = nil) ⇒ Hash{ Object => Numeric }

Shortest path length from source to all nodes using Bellman Ford algorithm

Parameters:

Returns:

  • (Hash{ Object => Numeric })

    path lengths from source to all nodes



350
351
352
353
# File 'lib/networkx/shortest_path/weighted.rb', line 350

def self.singlesource_bellmanford_path_length(graph, source, cutoff = nil)
  weight = get_weight(graph)
  help_bellman_ford(graph, [source], weight, nil, nil, nil, cutoff)
end

.singlesource_dijkstra(graph, source, target = nil, cutoff = nil) ⇒ Hash{ Object => Array<Object> }, Array<Object>

Computes shortest paths and path distances to all nodes/target from the given node

Parameters:

  • graph (Graph, DiGraph, MultiGraph, MultiDiGraph)

    a graph

  • source (Object)

    source

  • target (Object) (defaults to: nil)

    target

  • cutoff (Numeric, nil) (defaults to: nil)

    cutoff for the dijkstra algorithm

Returns:

  • (Hash{ Object => Array<Object> }, Array<Object>)

    paths for all nodes/target node from given node



113
114
115
# File 'lib/networkx/shortest_path/weighted.rb', line 113

def self.singlesource_dijkstra(graph, source, target = nil, cutoff = nil)
  multisource_dijkstra(graph, [source], target, cutoff)
end

.singlesource_dijkstra_path(graph, source, cutoff = nil) ⇒ Hash{ Object => Array<Object> }

Computes shortest paths to all nodes from the given node

Parameters:

Returns:

  • (Hash{ Object => Array<Object> })

    paths for all nodes from given node



135
136
137
# File 'lib/networkx/shortest_path/weighted.rb', line 135

def self.singlesource_dijkstra_path(graph, source, cutoff = nil)
  multisource_dijkstra_path(graph, [source], cutoff)
end

.singlesource_dijkstra_path_length(graph, source, cutoff = nil) ⇒ Hash{ Object => Numeric }

Computes shortest path lengths to all nodes from the given node

Parameters:

Returns:

  • (Hash{ Object => Numeric })

    path lengths for all nodes from given node



124
125
126
# File 'lib/networkx/shortest_path/weighted.rb', line 124

def self.singlesource_dijkstra_path_length(graph, source, cutoff = nil)
  multisource_dijkstra_path_length(graph, [source], cutoff)
end

.strong_product(g1, g2) ⇒ Graph, ...

Returns the strong product of two graphs

Parameters:

Returns:



157
158
159
160
161
162
163
164
165
# File 'lib/networkx/operators/product.rb', line 157

def self.strong_product(g1, g2)
  g = init_product_graph(g1, g2)
  g.add_nodes(node_product(g1, g2))
  g.add_edges(nodes_cross_edges(g1, g2))
  g.add_edges(edges_cross_nodes(g1, g2))
  g.add_edges(directed_edges_cross_edges(g1, g2))
  g.add_edges(undirected_edges_cross_edges(g1, g2)) unless g.directed?
  g
end

.symmetric_difference(g1, g2) ⇒ Graph, ...

Performs the symmetric difference of two graphs

Parameters:

Returns:

Raises:

  • (ArgumentError)


126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/networkx/operators/binary.rb', line 126

def self.symmetric_difference(g1, g2)
  result = g1.class.new

  raise ArgumentError, 'Arguments must be both Graphs or MultiGraphs!' unless g1.multigraph? == g2.multigraph?

  unless (g1.nodes(data: true).keys - g2.nodes(data: true).keys).empty?
    raise ArgumentError, 'Node sets must be equal!'
  end

  g1.nodes(data: true).each { |u, attrs| result.add_node(u, **attrs) }

  g1.adj.each do |u, u_edges|
    u_edges.each do |v, uv_attrs|
      if g1.multigraph?
        next if u > v && g1.instance_of?(MultiGraph)

        uv_attrs.each do |k, attrs|
          result.add_edge(u, v, **attrs) unless g2.edge?(u, v, k)
        end
      else
        result.add_edge(u, v, **uv_attrs) unless g2.edge?(u, v)
      end
    end
  end

  g2.adj.each do |u, u_edges|
    u_edges.each do |v, uv_attrs|
      next if u > v && g1.instance_of?(MultiGraph)

      if g2.multigraph?
        uv_attrs.each do |k, attrs|
          result.add_edge(u, v, **attrs) unless g1.edge?(u, v, k)
        end
      else
        result.add_edge(u, v, **uv_attrs) unless g1.edge?(u, v)
      end
    end
  end
  result
end

.tensor_product(g1, g2) ⇒ Graph, ...

Returns the tensor product of two graphs

Parameters:

Returns:



115
116
117
118
119
120
121
# File 'lib/networkx/operators/product.rb', line 115

def self.tensor_product(g1, g2)
  g = init_product_graph(g1, g2)
  g.add_nodes(node_product(g1, g2))
  g.add_edges(directed_edges_cross_edges(g1, g2))
  g.add_edges(undirected_edges_cross_edges(g1, g2)) unless g.directed?
  g
end

.to_matrix(graph, val, multigraph_weight = 'sum') ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/networkx/to_matrix.rb', line 2

def self.to_matrix(graph, val, multigraph_weight = 'sum')
  is_undirected = !graph.directed?
  is_multigraph = graph.multigraph?
  nodelen = graph.nodes(data: true).length

  m = Matrix.build(nodelen) { val }
  index = {}
  inv_index = {}
  ind = 0

  graph.nodes(data: true).each do |u, _|
    index[u] = ind
    inv_index[ind] = u
    ind += 1
  end

  if is_multigraph
    graph.adj.each do |u, edge_hash|
      edge_hash.each do |v, keys|
        all_weights = []
        keys.each do |_key, attrs|
          all_weights << attrs[:weight]
        end

        edge_attr = 0

        case multigraph_weight
        when 'sum'
          edge_attr = all_weights.sum
        when 'max'
          edge_attr = all_weights.max
        when 'min'
          edge_attr = all_weights.min
        end

        m[index[u], index[v]] = edge_attr
        m[index[v], index[u]] = edge_attr || 1 if is_undirected
      end
    end
  else
    graph.adj.each do |u, edge_hash|
      edge_hash.each do |v, attrs|
        m[index[u], index[v]] = (attrs[:weight] || 1)
        m[index[v], index[u]] = (attrs[:weight] || 1) if is_undirected
      end
    end
  end
  [m, inv_index]
end

.to_number_if_possible(str) ⇒ Object



42
43
44
45
46
47
48
49
50
51
# File 'lib/networkx/others/reads.rb', line 42

def self.to_number_if_possible(str)
  case str
  when /^[+-]?[0-9]+$/
    str.to_i
  when /^([+-]?\d*\.\d*)|(\d*[eE][+-]?\d+)$/
    str.to_f
  else
    str
  end
end

.topological_sort(graph) ⇒ Array<Object>

Returns the nodes arranged in the topologically sorted fashion

Parameters:

Returns:

  • (Array<Object>)

    Array of the nodes

Raises:

  • (ArgumentError)


33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/networkx/auxillary_functions/dag.rb', line 33

def self.topological_sort(graph)
  raise ArgumentError, 'Topological Sort not defined on undirected graphs!' unless graph.directed?

  nodes = []
  indegree_map = graph.nodes(data: true).each_key.map do |u|
    [u, graph.in_degree(u)] if graph.in_degree(u).positive?
  end.compact.to_h
  zero_indegree = graph.nodes(data: true).each_key.select { |u| graph.in_degree(u).zero? }

  until zero_indegree.empty?
    node = zero_indegree.shift
    raise ArgumentError, 'Graph changed during iteration!' unless graph.nodes(data: true).has_key?(node)

    graph.adj[node].each_key do |child|
      indegree_map[child] -= 1
      if indegree_map[child].zero?
        zero_indegree << child
        indegree_map.delete(child)
      end
    end
    nodes << node
  end
  raise ArgumentError, 'Graph contains cycle or graph changed during iteration!' unless indegree_map.empty?

  nodes
end

.undirected_edges_cross_edges(g1, g2) ⇒ Object

Returns the product of undirected edges with edges



51
52
53
54
55
56
57
58
59
# File 'lib/networkx/operators/product.rb', line 51

def self.undirected_edges_cross_edges(g1, g2)
  result = []
  edges_in_array(g1).each do |u, v, c|
    edges_in_array(g2).each do |x, y, d|
      result << [[v, x], [u, y], hash_product(c, d)]
    end
  end
  result
end

.union(g1, g2) ⇒ Graph, ...

Performs the union of two graphs

Parameters:

Returns:

Raises:

  • (ArgumentError)


197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/networkx/operators/binary.rb', line 197

def self.union(g1, g2)
  raise ArgumentError, 'Arguments must be both Graphs or MultiGraphs!' unless g1.multigraph? == g2.multigraph?

  new_graph = g1.class.new
  new_graph.graph.merge!(g1.graph)
  new_graph.graph.merge!(g2.graph)

  unless (g1.nodes(data: true).keys & g2.nodes(data: true).keys).empty?
    raise ArgumentError, 'Graphs must be disjoint!'
  end

  g1_edges = get_edges(g1)
  g2_edges = get_edges(g2)

  new_graph.add_nodes(g1.nodes(data: true).keys)
  new_graph.add_edges(g1_edges)
  new_graph.add_nodes(g2.nodes(data: true).keys)
  new_graph.add_edges(g2_edges)

  new_graph
end

.union_all(graphs) ⇒ Graph, ...

Performs the union of many graphs

Parameters:

Returns:

Raises:

  • (ArgumentError)


7
8
9
10
11
12
13
14
15
16
# File 'lib/networkx/operators/all.rb', line 7

def self.union_all(graphs)
  raise ArgumentError, 'Argument array is empty' if graphs.empty?

  result = graphs.shift

  graphs.each do |graph|
    result = NetworkX.union(result, graph)
  end
  result
end

.wiener_index(graph) ⇒ Numeric

Returns the wiener index of the graph

Parameters:

Returns:

  • (Numeric)

    wiener index of the graph



7
8
9
10
11
12
# File 'lib/networkx/auxillary_functions/wiener.rb', line 7

def self.wiener_index(graph)
  total = all_pairs_shortest_path_length(graph)
  wiener_ind = 0
  total.to_h.each { |_, distances| distances.to_h.each { |_, val| wiener_ind += val } }
  graph.directed? ? wiener_ind : wiener_ind / 2
end

Instance Method Details

#augment(path, inf, r_adj) ⇒ Object

Helper function for augmenting flow

Raises:

  • (ArgumentError)


108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/networkx/flow/shortestaugmentingpath.rb', line 108

def augment(path, inf, r_adj)
  flow = inf
  temp_path = path.clone
  u = temp_path.shift
  temp_path.each do |v|
    attr = r_adj[u][v]
    flow = [flow, attr[:capacity] - attr[:flow]].min
    u = v
  end
  raise ArgumentError, 'Infinite capacity path!' if flow * 2 > inf

  temp_path = path.clone
  u = temp_path.shift
  temp_path.each do |v|
    r_adj[u][v][:flow] += flow
    r_adj[v][u][:flow] -= flow
    u = v
  end
  flow
end