class ChaosDetector::GraphTheory::Appraiser

Attributes

adjacency_matrix[R]
cyclomatic_complexity[R]

Public Class Methods

new(graph) click to toggle source
# File lib/chaos_detector/graph_theory/appraiser.rb, line 11
def initialize(graph)
  @graph = graph
  @adjacency_matrix = nil
  @cyclomatic_complexity = nil
  @nodes_appraised = {}
end

Public Instance Methods

appraise(update_nodes:true) click to toggle source
# File lib/chaos_detector/graph_theory/appraiser.rb, line 18
def appraise(update_nodes:true)
  log('Appraising nodes.')
  @nodes_appraised = appraise_nodes!(update_nodes: update_nodes)
  
  # TODO: Store adjacency (to each other node) as a node metric?
  @adjacency_matrix = build_adjacency_matrix(@graph.nodes)

  # log('Measuring cyclomatic complexity.')
  # measure_cyclomatic_complexity

  log("Performed appraisal: %s" % to_s)
end
appraise_nodes!(update_nodes: true) click to toggle source

Returns Hash<Node, NodeMetrics>

update_nodes: Updates all nodes' :graph_props to appraisal metrics hash:
# File lib/chaos_detector/graph_theory/appraiser.rb, line 59
def appraise_nodes!(update_nodes: true)
  node_metrics = @graph.nodes.map do |node|
    metrics = appraise_node(node)
    [node, metrics]
  end.to_h

  if update_nodes
    node_metrics.each do |node, metrics|
      node.graph_props.merge!(metrics.to_h)
    end
  end

  node_metrics
end
build_adjacency_matrix(nodes) click to toggle source
# File lib/chaos_detector/graph_theory/appraiser.rb, line 74
def build_adjacency_matrix(nodes)
  matrix_dim = nodes.size
  # nodes = @graph.nodes
  Matrix.build(matrix_dim) do |row, col|
    node_src = nodes[row]
    node_dep = nodes[col]
    # puts "Adjacency found for #{node_src}, #{node_dep}: #{edge&.reduction}  / #{edge&.reduction&.reduction_sum.to_i}"
    # puts "Adjacency found for #{row}:#{col} -> #{node_src}, #{node_dep}: #{adjacency?(node_src, node_dep)}"
    adjacency?(node_src, node_dep)
  end
end
metrics_for(node:) click to toggle source
# File lib/chaos_detector/graph_theory/appraiser.rb, line 31
def metrics_for(node:)
  raise ArgumentError, 'Node is required' if node.nil?
  raise ArgumentError, ('Node [%s] has no metrics' % node) if !@nodes_appraised&.include?(node)
  log('has no metrics', object: node) if !@nodes_appraised&.include?(node)
  @nodes_appraised[node]
end
report() click to toggle source
# File lib/chaos_detector/graph_theory/appraiser.rb, line 42
def report
  buffy = [to_s]

  # buffy << "Circular References #{@circular_paths.length} / #{@circular_paths.uniq.length}"
  # buffy.concat(@circular_paths.map do |p|
  #   '  ' + p.map(&:title).join(' -> ')
  # end)

  # Gather nodes:
  buffy << 'Nodes:'
  buffy.concat(@nodes_appraised.map { |n, m| "  (#{n.title})[#{n.subtitle}]: #{m}" })

  buffy.join("\n")
end
to_s() click to toggle source
# File lib/chaos_detector/graph_theory/appraiser.rb, line 38
def to_s
  format('N: %d, E: %d', @graph.nodes.length, @graph.edges.length)
end

Private Instance Methods

adjacency?(node_src, node_dest) click to toggle source
# File lib/chaos_detector/graph_theory/appraiser.rb, line 125
def adjacency?(node_src, node_dest)
  edge = @graph.edges.find{|e| e.src_node == node_src && e.dep_node == node_dest }
  edge&.reduction&.reduction_sum.to_i
end
appraise_node(node) click to toggle source

For each node, measure fan-in(Ca) and fan-out(Ce)

# File lib/chaos_detector/graph_theory/appraiser.rb, line 89
def appraise_node(node)
  circular_routes, terminal_routes = appraise_node_routes(node)
  ChaosDetector::GraphTheory::NodeMetrics.new(
    node,
    afference: @graph.edges.count { |e| e.dep_node == node },
    efference: @graph.edges.count { |e| e.src_node == node },
    circular_routes: circular_routes,
    terminal_routes: terminal_routes
  )
end
appraise_node_routes(_node) click to toggle source
# File lib/chaos_detector/graph_theory/appraiser.rb, line 116
def appraise_node_routes(_node)
  # Traverse starting at each node to see if
  # and how many ways we come back to ourselves
  terminal_routes = []
  circular_routes = []

  [terminal_routes, circular_routes]
end
fan_in_nodes(node) click to toggle source
# File lib/chaos_detector/graph_theory/appraiser.rb, line 112
def fan_in_nodes(node)
  @graph.edges.find_all { |e| e.dep_node == node }.map(&:src_node)
end
fan_out_edges(node) click to toggle source
# File lib/chaos_detector/graph_theory/appraiser.rb, line 104
def fan_out_edges(node)
  @graph.edges.find_all { |e| e.src_node == node }
end
fan_out_nodes(node) click to toggle source
# File lib/chaos_detector/graph_theory/appraiser.rb, line 108
def fan_out_nodes(node)
  @graph.edges.find_all { |e| e.src_node == node }.map(&:dep_node)
end
log(msg, **opts) click to toggle source
# File lib/chaos_detector/graph_theory/appraiser.rb, line 100
def log(msg, **opts)
  ChaosUtils.log_msg(msg, subject: 'Appraiser', **opts)
end
measure_cyclomatic_complexity() click to toggle source
# File lib/chaos_detector/graph_theory/appraiser.rb, line 140
def measure_cyclomatic_complexity
  @circular_paths = []
  @full_paths = []
  traverse_nodes([@graph.root_node])
  @path_count_uniq = @circular_paths.uniq.count + @full_paths.uniq.count
  @cyclomatic_complexity = @graph.edges.count - @graph.nodes.count + (2 * @path_count_uniq)
end
node_distance(node_src, node_dep) click to toggle source

@return positive integer indicating distance in number of vertices from node_src to node_dep. If multiple routes, calculate shortest:

# File lib/chaos_detector/graph_theory/appraiser.rb, line 150
def node_distance(node_src, node_dep); end
node_matrix() click to toggle source
Coupling: Each node couplet (Example for 100 nodes, we'd have 100 * 99 potential couplets)
Capture how many other nodes depend upon both nodes in couplet [directly, indirectly]
Capture how many other nodes from other domains depend upon both [directly, indirectly]

TODO??

# File lib/chaos_detector/graph_theory/appraiser.rb, line 134
def node_matrix
  node_matrix = Matrix.build(@graph.nodes.length) do |row, col|
  end
  node_matrix
end
normalize(ary, property, norm_property) click to toggle source
# File lib/chaos_detector/graph_theory/appraiser.rb, line 152
def normalize(ary, property, norm_property)
  vector = Vector.elements(ary.map { |obj| obj.send(property)})
  vector = vector.normalize
  ary.each_with_index do |obj, i|
    obj.send("#{norm_property}=", vector[i])
  end
  ary
end