Current section
Files
Jump to
Current section
Files
lib/bitgraph.ex
defmodule BitGraph do
@moduledoc """
Documentation for `BitGraph`.
"""
@doc """
"""
@type t :: map()
alias BitGraph.{V, E, Adjacency, Neighbor}
alias Iter.{Iterable, Iterable.Mapper}
import BitGraph.Common
def new(opts \\ []) do
opts = Keyword.merge(default_opts(), opts)
%{
opts: opts,
vertices: V.init_vertices(opts),
edges: E.init_edges(opts),
adjacency: Adjacency.init_adjacency_table(opts),
owner: self()
}
end
def copy(%{adjacency: adjacency} = graph) do
Map.put(graph, :adjacency, Adjacency.copy(adjacency, graph.edges))
end
@doc """
An induced subgraph on `subgraph_vertices`
"""
def subgraph(graph, subgraph_vertices, mode \\ :detached)
## Makes a 'detached' subgraph that only contains
## subset of vertices and edges between them.
##
def subgraph(graph, subgraph_vertices, :detached) do
Enum.reduce(BitGraph.vertices(graph), copy(graph), fn existing_vertex, acc ->
if !Iterable.member?(subgraph_vertices, existing_vertex) do
BitGraph.delete_vertex(acc, existing_vertex)
else
acc
end
end)
end
## Maps the graph onto a subgraph.
## The vertex implementation will use the subgraph option
## to filter vertices accordingly.
## The graph structure will not be modified.
def subgraph(graph, subgraph_vertices, :mapped) do
Map.put(
graph,
:subgraph,
## :subgraph keeps the set of vertex indices in the form of iterator
Enum.reduce(subgraph_vertices, MapSet.new(), fn vertex, acc ->
case V.get_vertex_index(graph, vertex) do
nil -> acc
idx -> MapSet.put(acc, idx)
end
end)
)
end
def strong_components(graph, opts \\ []) do
graph
|> update_opts(opts)
|> BitGraph.Algorithm.strong_components(opts)
|> Enum.map(fn component ->
MapSet.new(component, fn vertex_idx -> BitGraph.V.get_vertex(graph, vertex_idx) end)
end)
end
def strongly_connected?(graph, opts \\ []) do
BitGraph.Algorithm.strongly_connected?(graph, opts)
end
def bipartite_matching(graph, opts \\ []) do
BitGraph.Algorithm.bipartite_matching(graph, Keyword.put(opts, :process_mode, :both))
end
def a_star(graph, start, goal, opts \\ []) do
BitGraph.Algorithm.a_star(graph, start, goal, Keyword.put(opts, :process_mode, :both))
end
def mst(graph, opts \\ []) do
BitGraph.Algorithm.MST.Kruskal.run(graph, opts)
end
def default_opts() do
[
max_vertices: 1024
]
end
defp core_opts_names() do
[:neighbor_finder]
end
def get_opts(graph) do
graph[:opts]
end
def update_opts(graph, []) do
graph
end
def update_opts(graph, opts) do
Map.update!(graph, :opts, fn current_opts ->
Keyword.merge(
current_opts,
Keyword.take(opts, core_opts_names())
)
end)
end
def get_opt(graph, option) do
get_opts(graph)[option]
end
def set_opt(graph, option, value) do
put_in(graph, [:opts, option], value)
end
def set_neighbor_finder(graph, neighbor_finder) do
set_opt(graph, :neighbor_finder, neighbor_finder)
end
def get_neighbor_finder(graph) do
Neighbor.get_neighbor_finder(graph)
end
def get_subgraph(graph) do
graph[:subgraph]
end
def add_vertex(graph, vertex, opts \\ []) do
graph
|> V.add_vertex(vertex, opts)
end
def add_vertices(graph, vertices) do
Enum.reduce(vertices, graph, fn
{vertex, opts}, acc when is_list(opts) ->
add_vertex(acc, vertex, opts)
vertex, acc ->
add_vertex(acc, vertex)
end)
end
def isolated_vertex?(graph, vertex) do
vertex && V.isolated?(graph, V.get_vertex_index(graph, vertex))
end
def leaf_vertex?(graph, vertex) do
vertex && V.leaf?(graph, V.get_vertex_index(graph, vertex))
end
def delete_vertex(graph, vertex) do
case V.get_vertex_index(graph, vertex) do
nil ->
graph
vertex_index ->
graph
|> E.delete_edges(vertex_index)
|> V.delete_vertex(vertex)
end
end
def get_vertex(graph, vertex, opts \\ []) do
vertex_index = V.get_vertex_index(graph, vertex)
V.get_vertex(graph, vertex_index, opts)
end
def update_vertex(graph, vertex, opts \\ []) do
vertex_index = V.get_vertex_index(graph, vertex)
V.update_vertex(graph, vertex_index, opts)
end
def vertices(graph, mapper \\ & &1.vertex) do
V.vertices(graph, mapper)
end
def vertex_indices(graph) do
V.vertex_indices(graph)
end
def max_index(graph) do
vertex_indices(graph) |> Enum.max()
end
def num_vertices(graph) do
V.num_vertices(graph)
end
def add_edge(graph, {_edge_key, %E{} = edge}) do
add_edge(graph, edge)
end
def add_edge(graph, %E{from: from, to: to, opts: opts}) do
add_edge(graph, from, to, opts)
end
def add_edge(graph, from, to, opts \\ []) do
graph
|> V.add_vertex(from, opts)
|> V.add_vertex(to, opts)
|> then(fn graph ->
from_index = V.get_vertex_index(graph, from)
to_index = V.get_vertex_index(graph, to)
E.add_edge(graph, from_index, to_index)
Map.update(graph, :edges, %{}, fn edges ->
Map.put(edges, {from_index, to_index}, E.new(from, to, opts))
end)
end)
end
def add_edges(graph, edges) do
Enum.reduce(edges, graph, fn
{from, to}, acc ->
add_edge(acc, from, to)
{from, to, opts}, acc ->
add_edge(acc, from, to, opts)
%E{} = edge, acc ->
add_edge(acc, edge)
end)
end
def delete_edge(graph, {_edge_key, %E{} = edge}) do
delete_edge(graph, edge)
end
def delete_edge(graph, %E{from: from, to: to} = _edge) do
delete_edge(graph, from, to)
end
def delete_edge(graph, from, to) do
from_index = V.get_vertex_index(graph, from)
to_index = V.get_vertex_index(graph, to)
(from_index && to_index &&
E.delete_edge(graph, from_index, to_index)) ||
graph
end
def delete_edges(graph, edges) do
Enum.reduce(edges, graph, fn {from, to}, acc ->
delete_edge(acc, from, to)
end)
end
def get_edge(graph, from, to) do
E.get_edge(
graph,
V.get_vertex_index(graph, from),
V.get_vertex_index(graph, to)
)
end
def edges(graph) do
E.edges(graph)
end
def num_edges(graph) do
E.edges(graph) |> MapSet.size()
end
def edges(graph, vertex, edge_fun \\ &default_edge_info/3, opts \\ []) do
MapSet.union(
in_edges(graph, vertex, edge_fun, opts),
out_edges(graph, vertex, edge_fun, opts)
)
end
def in_edges(graph, vertex, edge_fun \\ &default_edge_info/3, opts \\ []) do
graph = update_opts(graph, opts)
edges_impl(graph, V.get_vertex_index(graph, vertex), edge_fun, :in, opts)
end
def out_edges(graph, vertex, edge_fun \\ &default_edge_info/3, opts \\ []) do
graph = update_opts(graph, opts)
edges_impl(graph, V.get_vertex_index(graph, vertex), edge_fun, :out, opts)
end
def in_neighbors(graph, vertex, opts \\ []) do
graph = update_opts(graph, opts)
case V.get_vertex_index(graph, vertex) do
nil -> MapSet.new()
vertex_index -> V.in_neighbors(graph, vertex_index)
end
|> shape(graph, vertex, opts)
end
def out_neighbors(graph, vertex, opts \\ []) do
graph = update_opts(graph, opts)
case V.get_vertex_index(graph, vertex) do
nil -> MapSet.new()
vertex_index -> V.out_neighbors(graph, vertex_index)
end
|> shape(graph, vertex, opts)
end
defp shape(neighbors, graph, vertex, opts) do
shape = Keyword.get(opts, :shape, :set)
transform_neighbors(graph, vertex, neighbors, shape)
end
## Neighbors as an enum of vertices.
def transform_neighbors(graph, vertex, neighbors, :set) do
transform_neighbors(graph, vertex, neighbors, :iterator)
|> iterate(MapSet.new(), fn neighbor, acc -> {:cont, MapSet.put(acc, neighbor)} end)
end
## Neighbors as an iterator of vertices
def transform_neighbors(graph, _vertex, neighbors, :iterator) do
Mapper.new(neighbors, fn vertex_index ->
V.get_vertex(graph, vertex_index)
end)
end
## Neighbors
def transform_neighbors(graph, vertex, neighbors, transform_fun)
when is_function(transform_fun, 3) do
transform_fun.(graph, vertex, neighbors)
end
def neighbors(graph, vertex, opts \\ []) do
Iterable.concat([out_neighbors(graph, vertex, opts), in_neighbors(graph, vertex, opts)])
end
def in_degree(graph, vertex, opts \\ []) do
in_neighbors(graph, vertex, opts) |> MapSet.size()
end
def degree(graph, vertex, opts \\ []) do
in_degree(graph, vertex, opts) + out_degree(graph, vertex, opts)
end
def out_degree(graph, vertex, opts \\ []) do
out_neighbors(graph, vertex, opts) |> MapSet.size()
end
## Show the graph (intended for debugging)
def show_graph(graph, context \\ nil) do
%{
context: context,
vertices: BitGraph.vertices(graph) |> Iterable.to_list(),
edges:
BitGraph.E.edges(graph)
|> Iterable.map(fn %{from: from_index, to: to_index} ->
{
BitGraph.V.get_vertex(graph, from_index),
BitGraph.V.get_vertex(graph, to_index)
}
end)
|> Iterable.to_list()
|> Enum.group_by(fn {from, _to} -> from end, fn {_from, to} -> to end)
}
end
defp edges_impl(graph, vertex_index, edge_info_fun, direction, _opts)
when is_integer(vertex_index) do
edges =
(direction == :in && E.in_edges(graph, vertex_index)) || E.out_edges(graph, vertex_index)
iterate(edges, MapSet.new(), fn %E{from: from, to: to}, acc ->
{:cont,
case edge_info_fun.(from, to, graph) do
nil -> acc
edge_info -> MapSet.put(acc, edge_info)
end}
end)
end
defp edges_impl(_graph, vertex_index, _edge_info_fun, _direction, _opts)
when is_nil(vertex_index) do
MapSet.new()
end
defp default_edge_info(from, to, %{edges: edges} = _graph)
when is_integer(from) and is_integer(to) do
Map.get(edges, {from, to}, E.new(from, to))
end
defp default_edge_info(from, to, graph) do
default_edge_info(V.get_vertex_index(graph, from), V.get_vertex_index(graph, to), graph)
end
end