Rebuild graph on top of RGL

This commit is contained in:
Chris Roberts 2021-10-22 16:07:43 -07:00 committed by Paul Hinze
parent 0a20c9d665
commit 482bf15d57
No known key found for this signature in database
GPG Key ID: B69DEDF2D55501C0

View File

@ -1,331 +1,239 @@
require "rgl/adjacency"
require "rgl/traversal"
require "rgl/dijkstra"
require "rgl/topsort"
module VagrantPlugins
module CommandServe
class Mappers
module Internal
class Graph
class Graph < RGL::DirectedAdjacencyGraph
autoload :Mappers, Vagrant.source_root.join("plugins/commands/serve/mappers/internal/graph/mappers").to_s
autoload :Search, Vagrant.source_root.join("plugins/commands/serve/mappers/internal/graph/search").to_s
autoload :Topological, Vagrant.source_root.join("plugins/commands/serve/mappers/internal/graph/topological").to_s
autoload :Vertex, Vagrant.source_root.join("plugins/commands/serve/mappers/internal/graph/vertex").to_s
autoload :WeightedVertex, Vagrant.source_root.join("plugins/commands/serve/mappers/internal/graph/weighted_vertex").to_s
include Util::HasLogger
# Default weight used for weighted vetices
# when no weight is provided
DEFAULT_WEIGHT = 1_000_000
# Marker value used for lookups
VERTEX_ID = :vertex_id
DEFAULT_WEIGHT = 1000
# @return [Hash<Object, Vertex>] list of vertices within graph
attr_reader :vertex_map
# @return [Hash<Object, Hash<Object, Symbol>>] incoming adjecency list
attr_reader :adjecency_in
# @return [Hash<Object, Array<WeightedVertex>] weighted incoming adjecency list
attr_reader :adjecency_in_weight
# @return [Hash<Object, Hash<Object, Symbol>>] outgoing adjecency list
attr_reader :adjecency_out
# @return [Hash<Object, Array<WeightedVertex>] weighted outgoing adjecency list
attr_reader :adjecency_out_weight
# Create a new Graph
def initialize
def initialize(*_)
@vertex_map = {}
@adjecency_in = {}
@adjecency_in_weight = {}
@adjecency_out = {}
@adjecency_out_weight = {}
@m = Mutex.new
super
end
def initialize_copy(orig)
super
@vertex_map = orig.instance_eval { @vertex_map }.dup
@vertices_dict = @vertices_dict.dup
@vertices_dict.keys.each do |v|
edges = @vertices_dict[v]
@vertices_dict[v] = {in: edges[:in].dup, out: edges[:out].dup}
end
end
def to_a
@vertices_dict.keys
end
def each_out_vertex(v, &block)
v = vertex_for(v)
if !has_vertex?(v)
raise "No vertex `#{v}' found in graph"
end
@vertices_dict[v][:out].each(&block)
end
alias :each_adjacent :each_out_vertex
def out_vertices(v)
v = vertex_for(v)
if !has_vertex?(v)
raise "No vertex `#{v}' found in graph"
end
@vertices_dict[v][:out].to_a
end
alias :adjacent_vertices :out_vertices
def out_degree(v)
v = vertex_for(v)
if !has_vertex?(v)
raise "No vertex `#{v}' found in graph"
end
@vertices_dict[v][:out].size
end
def each_in_vertex(v, &block)
v = vertex_for(v)
if !has_vertex?(v)
raise "No vertex `#{v}' found in graph"
end
@vertices_dict[v][:in].each(&block)
end
def in_vertices(v)
v = vertex_for(v)
if !has_vertex?(v)
raise "No vertex `#{v}' found in graph"
end
@vertices_dict[v][:in].to_a
end
def in_degree(v)
v = vertex_for(v)
if !has_vertex?(v)
raise "No vertex `#{v}' found in graph"
end
@vertices[v][:in].size
end
# @return [Array<Vertex>] list of vertices
def vertices
@m.synchronize do
vertex_map.values
@vertices_dict.keys
end
def num_vertices
@vertices_dict.size
end
def num_edges
@vertices_dict.each_value.inject(0) do |count, edges|
count + edges[:out].size
end
end
# Create a copy of the current Graph
#
# @return [Graph]
def copy
@m.synchronize do
self.class.new.tap do |g|
# Copy our vertices list
g.vertex_map.replace(vertex_map.dup)
# Copy incoming edges list
new_in = Hash.new.tap do |nin|
adjecency_in.each_pair do |k, v|
nin[k] = v.dup
end
end
g.adjecency_in.replace(new_in)
# Copy outgoing edges list
new_out = Hash.new.tap do |nout|
adjecency_out.each_pair do |k, v|
nout[k] = v.dup
end
end
g.adjecency_out.replace(new_out)
# Copy incoming edge weights
in_w = Hash.new.tap do |win|
adjecency_in_weight.each_pair do |k, v|
win[k] = v.dup
end
end
# Copy outgoing edge weights
g.adjecency_in_weight.replace(in_w)
out_w = Hash.new.tap do |wout|
adjecency_out_weight.each_pair do |k, v|
wout[k] = v.dup
end
end
g.adjecency_out_weight.replace(out_w)
end
end
def has_edge?(u, v)
u = vertex_for(u)
v = vertex_for(v)
has_vertex?(u) && @vertices_dict[u][0].include?(v)
end
# Create a reversed copy of the current Graph
#
# @return [Graph]
def reverse
@m.synchronize do
self.class.new.tap do |g|
# Copy our vertices list
g.vertex_map.replace(vertex_map.dup)
# Transfer incoming edges list to outgoing edges
new_in = Hash.new.tap do |nin|
adjecency_in.each_pair do |k, v|
nin[k] = v.dup
end
end
g.adjecency_out.replace(new_in)
# Transfer outgoing edges list to incoming edges
new_out = Hash.new.tap do |nout|
adjecency_out.each_pair do |k, v|
nout[k] = v.dup
end
end
g.adjecency_in.replace(new_out)
in_w = Hash.new.tap do |win|
adjecency_in_weight.each_pair do |k, v|
win[k] = v.dup
end
end
# Set out vertices as in
g.adjecency_out_weight.replace(in_w)
out_w = Hash.new.tap do |wout|
adjecency_out_weight.each_pair do |k, v|
wout[k] = v.dup
end
end
# Set in vertices as out
g.adjecency_in_weight.replace(out_w)
end
end
end
# List of incoming vertices and their weights
# to the given vertex
#
# @param v [Vertex]
# @return [Hash<Vertex, Integer>]
def weighted_edges_in(v)
@m.synchronize do
v = add_vertex(v)
Hash.tap do |wei|
Array(adjecency_in_weight[v.hash_code]).each do |vrt|
wei[vertex_map[vrt.hash_code]] = vrt.weight
end
end
end
end
# List of outgoing vertices and their weights
# from the given vertex
#
# @param v [Vertex]
# @return [Hash<Vertex, Integer>]
def weighted_edges_out(v)
@m.synchronize do
v = add_vertex(v)
Hash.tap do |wei|
Array(adjecency_out_weight[v.hash_code]).each do |vrt|
wei[vertex_map[vrt.hash_code]] = vrt.weight
end
end
end
end
# List of incoming vertices to the given vertex
#
# @param v [Vertex]
# @return [Array<Vertex>]
def edges_in(v)
@m.synchronize do
v = add_vertex(v)
adjecency_in_weight[v.hash_code].sort_by(&:weight).map do |vrt|
vertex_map[vrt.hash_code]
end
end
end
# List of outgoing vertices from the given vertex
#
# @param v [Vertex]
# @return [Array<Vertex>]
def edges_out(v)
@m.synchronize do
v = add_vertex(v)
adjecency_out_weight[v.hash_code].sort_by(&:weight).map do |vrt|
vertex_map[vrt.hash_code]
end
end
end
# Add a vertex to the graph. Vertices are
# registered by the vertex's hash code value,
# so the returned object may not be the same
# object that was provided.
#
# @param v [Vertex]
# @return [Vertex]
def add(v)
@m.synchronize do
add_vertex(v)
end
end
# Remove a vertex from the graph
#
# @param v [Vertex]
# @return [Vertex]
def remove(v)
@m.synchronize do
v = add_vertex(v)
vertex_map.delete(v.hash_code)
adjecency_out.delete(v.hash_code)
adjecency_in.delete(v.hash_code)
adjecency_out_weight.delete(v.hash_code)
adjecency_in_weight.delete(v.hash_code)
adjecency_in.each_pair do |_, invrt|
invrt.delete(v.hash_code)
end
adjecency_in_weight.values.each do |list|
list.delete_if { |vrt| vrt.hash_code == v.hash_code }
end
adjecency_out.each_pair do |_, outvrt|
outvrt.delete(v.hash_code)
end
adjecency_out_weight.values.each do |list|
list.delete_if { |vrt| vrt.hash_code == v.hash_code }
end
v
end
end
# Add an edge from one vertex to another
#
# @param from [Vertex]
# @param to [Vertex]
# @return [self]
def add_edge(from, to)
add_weighted_edge(from, to, DEFAULT_WEIGHT)
self
end
# Add a weighted edge from one vertex to another
#
# @param from [Vertex]
# @param to [Vertex]
# @param weight [Integer]
# @return [self]
def add_weighted_edge(from, to, weight)
@m.synchronize do
from = add_vertex(from)
to = add_vertex(to)
adjecency_out[from.hash_code][to.hash_code] = VERTEX_ID
adjecency_out_weight[from.hash_code] << WeightedVertex.new(
code: to.hash_code,
weight: weight
)
adjecency_in[to.hash_code][from.hash_code] = VERTEX_ID
adjecency_in_weight[to.hash_code] << WeightedVertex.new(
code: from.hash_code,
weight: weight
)
end
self
end
# Remove an edge from one vertex to another
#
# @param from [Vertex]
# @param to [Vertex]
# @return [self]
def remove_edge(from, to)
@m.synchronize do
from = add_vertex(from)
to = add_vertex(to)
adjecency_in[to.hash_code].delete(from.hash_code)
adjecency_in_weight[to.hash_code].delete_if { |vrt|
vrt.hash_code == from.hash_code }
adjecency_out[from.hash_code].delete(to.hash_code)
adjecency_out_weight[from.hash_code].delete_if { |vrt|
vrt.hash_code == to.hash_code }
end
self
end
# Finalize the graph for use. This will ensure all
# edges are properly sorted if weighted.
def finalize!
# @m.synchronize do
# adjecency_in_weight.each_pair do |code, list|
# list.sort_by!(&:weight)
# adjecency_in[code] = Hash[list.map{ |w| [w.hash_code, VERTEX_ID] }]
# end
# adjecency_out_weight.each_pair do |code, list|
# list.sort_by!(&:weight)
# adjecency_out[code] = Hash[list.map{ |w| [w.hash_code, VERTEX_ID] }]
# end
# end
self
end
protected
# Adds a vertex to the graph and initializes
# edge data structures. If a hash code for the
# vertex has already been registered, the registered
# vertex will be returned (which may be different than
# the provided vertex)
#
# @param v [Vertex]
# @return [Vertex]
def add_vertex(v)
if !v.is_a?(Vertex)
raise TypeError,
"Expected type `Vertex', got `#{v.class}'"
end
if vertex_map.key?(v.hash_code)
return vertex_map[v.hash_code]
if @vertex_map.key?(v.hash_code)
v = @vertex_map[v.hash_code]
else
if !v.is_a?(WeightedVertex)
v = WeightedVertex.new(v, weight: DEFAULT_WEIGHT)
end
@vertex_map[v.hash_code] = v
end
adjecency_in[v.hash_code] = {}
adjecency_in_weight[v.hash_code] = []
adjecency_out[v.hash_code] = {}
adjecency_out_weight[v.hash_code] = []
vertex_map[v.hash_code] = v
@vertices_dict[v] ||= {
in: @edgelist_class.new,
out: @edgelist_class.new,
}
v
end
def add_edge(u, v)
u = add_vertex(u) # ensure key
v = add_vertex(v) # ensure key
basic_add_edge(u, v)
end
def remove_vertex(v)
v = vertex_for(v)
if has_vertex?(v)
edges = @vertices_dict[v]
@vertices_dict.delete(v)
edges[:in].each do |parent|
@vertices_dict[parent][:out].delete(v)
end
edges[:out].each do |child|
@vertices_dict[child][:in].delete(v)
end
@vertex_map.delete(v.hash_code)
end
v
end
def remove_edge(u, v)
u = vertex_for(u)
v = vertex_for(v)
if has_vertex?(u) && has_vertex?(v)
if @vertices_dict[u][:out].delete?(v)
@vertices_dict[v][:in].delete(u)
end
end
self
end
def edgelist_class=(klass)
@vertices_dict.keys.each do |v|
edges = @vertices_dict[v]
@vertices_dict[v] = {
in: klass.new(edges[:in].to_a),
out: klass.new(edges[:out].to_a),
}
end
self
end
def reverse
result = dup
result.reverse!
end
def reverse!
@vertices_dict.keys.each do |v|
edges = @vertices_dict[v]
@vertices_dict[v] = {
in: edges[:out],
out: edges[:in],
}
end
self
end
def shortest_path(source, target)
dijkstra_shortest_path(edge_weights_map, source, target)
end
def shortest_paths(source, target)
dijkstra_shortest_paths(edge_weights_map, source, target)
end
def edge_weights_map
Hash.new.tap do |edge_map|
each_edge do |*edges|
edge_map[edges] = edges.map(&:weight).inject(&:+)
end
end
end
def vertex_for(v)
@vertex_map[v.hash_code]
end
def to_s
"<#{self.class.name}:#{object_id} num_vertices=#{vertices.size}>"
end
def inspect
vinfo = vertices.map do |v|
ins = in_vertices(v).map do |iv|
" -> #{iv}"
end.join("\n")
outs = out_vertices(v).map do |ov|
" <- #{ov}"
end.join("\n")
" " + [v].tap { |content|
content << ins if !ins.empty?
content << outs if !outs.empty?
}.join("\n")
end.join("\n")
"<#{self.class.name}:#{object_id} num_vertices=#{vertices.size} vertices=\n#{vinfo}\n>"
end
protected
def basic_add_edge(u, v)
@vertices_dict[u][:out].add(v)
@vertices_dict[v][:in].add(u)
end
end
end