Class: Ibex::Impact::Propagation
- Inherits:
-
Object
- Object
- Ibex::Impact::Propagation
- Defined in:
- lib/ibex/impact/propagation.rb,
sig/ibex/impact/propagation.rbs
Overview
Performs deterministic forward propagation over a dependency graph.
Instance Method Summary collapse
- #build_nodes(component_nodes, components, kind, witnesses) ⇒ Hash[Integer, Node]
- #component_adjacency(adjacency, component_for, component_count) ⇒ Array[Array[Integer]]
- #component_index(components, size) ⇒ Array[Integer]
-
#initialize(graph) ⇒ Propagation
constructor
A new instance of Propagation.
- #normalize_seeds(seeds) ⇒ Array[Integer]
- #propagate(seeds, kind = :all, max_depth: nil) ⇒ Hash[Integer, Node] (also: #call)
- #symbol_witnesses(seeds, adjacency, kind) ⇒ Hash[Integer, Array[Edge]]
- #traverse_components(seeds, component_for, component_edges, max_depth) ⇒ Hash[Integer, Integer]
- #validate_depth(max_depth) ⇒ void
Constructor Details
#initialize(graph) ⇒ Propagation
Returns a new instance of Propagation.
31 32 33 |
# File 'lib/ibex/impact/propagation.rb', line 31 def initialize(graph) @graph = graph end |
Instance Method Details
#build_nodes(component_nodes, components, kind, witnesses) ⇒ Hash[Integer, Node]
133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/ibex/impact/propagation.rb', line 133 def build_nodes(component_nodes, components, kind, witnesses) result = {} #: Hash[Integer, Node] component_nodes.each do |component, distance| members = components.fetch(component).sort members.each do |symbol| result[symbol] = Node.new( symbol: symbol, distance: distance, witness: witnesses.fetch(symbol), kind: kind, component: members ) end end result.sort.to_h end |
#component_adjacency(adjacency, component_for, component_count) ⇒ Array[Array[Integer]]
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/ibex/impact/propagation.rb', line 77 def component_adjacency(adjacency, component_for, component_count) result = Array.new(component_count) { [] } #: Array[Array[Integer]] adjacency.each_with_index do |successors, source| source_component = component_for.fetch(source) successors.each do |target| target_component = component_for.fetch(target) next if source_component == target_component result[source_component] << target_component end end result.each do |successors| successors.uniq! successors.sort! end result end |
#component_index(components, size) ⇒ Array[Integer]
70 71 72 73 74 |
# File 'lib/ibex/impact/propagation.rb', line 70 def component_index(components, size) result = Array.new(size, 0) #: Array[Integer] components.each_with_index { |members, id| members.each { |member| result[member] = id } } result end |
#normalize_seeds(seeds) ⇒ Array[Integer]
60 61 62 63 64 65 66 67 |
# File 'lib/ibex/impact/propagation.rb', line 60 def normalize_seeds(seeds) seeds.uniq.sort.each do |id| unless @graph.grammar.symbol_by_id(id) raise ArgumentError, "impact seed #{id.inspect} is not a grammar symbol" end end end |
#propagate(seeds, kind = :all, max_depth: nil) ⇒ Hash[Integer, Node] Also known as: call
36 37 38 39 40 41 42 43 44 45 46 |
# File 'lib/ibex/impact/propagation.rb', line 36 def propagate(seeds, kind = :all, max_depth: nil) validate_depth(max_depth) selected = normalize_seeds(seeds) adjacency = @graph.adjacency(kind) components = Analysis::Digraph.send(:strongly_connected_components, adjacency) component_for = component_index(components, adjacency.length) component_edges = component_adjacency(adjacency, component_for, components.length) component_nodes = traverse_components(selected, component_for, component_edges, max_depth) witnesses = symbol_witnesses(selected, adjacency, kind) build_nodes(component_nodes, components, kind, witnesses) end |
#symbol_witnesses(seeds, adjacency, kind) ⇒ Hash[Integer, Array[Edge]]
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/ibex/impact/propagation.rb', line 115 def symbol_witnesses(seeds, adjacency, kind) edges = @graph.edges(kind).group_by { |edge| [edge.source, edge.target] } queue = seeds.uniq.sort.map { |seed| [seed, []] } #: Array[[Integer, Array[Edge]]] result = {} #: Hash[Integer, Array[Edge]] until queue.empty? symbol, witness = queue.shift next if result.key?(symbol) result[symbol] = witness adjacency.fetch(symbol).each do |target| edge = edges.fetch([symbol, target]).fetch(0) queue << [target, witness + [edge]] end end result end |
#traverse_components(seeds, component_for, component_edges, max_depth) ⇒ Hash[Integer, Integer]
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/ibex/impact/propagation.rb', line 96 def traverse_components(seeds, component_for, component_edges, max_depth) queue = seeds.uniq.sort.map do |seed| [component_for.fetch(seed), 0] end #: Array[[Integer, Integer]] result = {} #: Hash[Integer, Integer] until queue.empty? component, distance = queue.shift next if result.key?(component) next if max_depth && distance > max_depth result[component] = distance component_edges.fetch(component).each do |target| queue << [target, distance + 1] end end result end |
#validate_depth(max_depth) ⇒ void
This method returns an undefined value.
53 54 55 56 57 |
# File 'lib/ibex/impact/propagation.rb', line 53 def validate_depth(max_depth) return if max_depth.nil? || (max_depth.is_a?(Integer) && max_depth >= 0) raise ArgumentError, "impact depth must be a non-negative integer" end |