defmodule Graph do @moduledoc false # This module defines a graph data structure, which supports directed and undirected graphs, in both acyclic and cyclic forms. # It also defines the API for creating, manipulating, and querying that structure. # As far as memory usage is concerned, `Graph` should be fairly compact in memory, but if you want to do a rough # comparison between the memory usage for a graph between `libgraph` and `digraph`, use `:digraph.info/1` and # `Graph.info/1` on the two graphs, and both results will contain memory usage information. Keep in mind we don't have a precise # way to measure the memory usage of a term in memory, whereas ETS is able to give a more precise answer, but we do have # a fairly good way to estimate the usage of a term, and we use that method within `libgraph`. # The Graph struct is structured like so: # - A map of vertex ids to vertices (`vertices`) # - A map of vertex ids to their out neighbors (`out_edges`), # - A map of vertex ids to their in neighbors (`in_edges`), effectively the transposition of `out_edges` # - A map of vertex ids to vertex labels (`vertex_labels`), (labels are only stored if a non-nil label was provided) # - A map of edge ids (where an edge id is simply a tuple of `{vertex_id, vertex_id}`) to a map of edge metadata (`edges`) # - Edge metadata is a map of `label => weight`, and each entry in that map represents a distinct edge. This allows # us to support multiple edges in the same direction between the same pair of vertices, but for many purposes simply # treat them as a single logical edge. # This structure is designed to be as efficient as possible once a graph is built, but it turned out that it is also # quite efficient for manipulating the graph as well. For example, splitting an edge and introducing a new vertex on that # edge can be done with very little effort. We use vertex ids everywhere because we can generate them without any lookups, # we don't incur any copies of the vertex structure, and they are very efficient as keys in a map. defstruct in_edges: %{}, out_edges: %{}, edges: %{}, vertex_labels: %{}, vertices: %{}, type: :directed, vertex_identifier: &Graph.Utils.vertex_id/1 alias Graph.{Edge, EdgeSpecificationError} @typedoc """ Identifier of a vertex. By default a non_neg_integer from `Graph.Utils.vertex_id/1` utilizing `:erlang.phash2`. """ @type vertex_id :: non_neg_integer() | term() @type vertex :: term @type label :: term @type edge_weight :: integer | float @type edge_key :: {vertex_id, vertex_id} @type edge_value :: %{label => edge_weight} @type graph_type :: :directed | :undirected @type vertices :: %{vertex_id => vertex} @type t :: %__MODULE__{ in_edges: %{vertex_id => MapSet.t()}, out_edges: %{vertex_id => MapSet.t()}, edges: %{edge_key => edge_value}, vertex_labels: %{vertex_id => term}, vertices: %{vertex_id => vertex}, type: graph_type, vertex_identifier: (vertex() -> term()) } @type graph_info :: %{ :num_edges => non_neg_integer(), :num_vertices => non_neg_integer(), :size_in_bytes => number(), :type => :directed | :undirected } @doc """ Creates a new graph using the provided options. ## Options - `type: :directed | :undirected`, specifies what type of graph this is. Defaults to a `:directed` graph. - `vertex_identifier`: a function which accepts a vertex and returns a unique identifier of said vertex. Defaults to `Graph.Utils.vertex_id/1`, a hash of the whole vertex utilizing `:erlang.phash2/2`. ## Example iex> Graph.new() #Graph iex> g = Graph.new(type: :undirected) |> Graph.add_edges([{:a, :b}, {:b, :a}]) ...> Graph.edges(g) [%Graph.Edge{v1: :a, v2: :b}] iex> g = Graph.new(type: :directed) |> Graph.add_edges([{:a, :b}, {:b, :a}]) ...> Graph.edges(g) [%Graph.Edge{v1: :a, v2: :b}, %Graph.Edge{v1: :b, v2: :a}] iex> g = Graph.new(vertex_identifier: fn v -> :erlang.phash2(v) end) |> Graph.add_edges([{:a, :b}, {:b, :a}]) ...> Graph.edges(g) [%Graph.Edge{v1: :a, v2: :b}, %Graph.Edge{v1: :b, v2: :a}] """ def new(opts \\ []) do type = Keyword.get(opts, :type) || :directed vertex_identifier = Keyword.get(opts, :vertex_identifier) || (&Graph.Utils.vertex_id/1) %__MODULE__{type: type, vertex_identifier: vertex_identifier} end @doc """ Returns a map of summary information about this graph. NOTE: The `size_in_bytes` value is an estimate, not a perfectly precise value, but should be close enough to be useful. ## Example iex> g = Graph.new |> Graph.add_vertices([:a, :b, :c, :d]) ...> g = g |> Graph.add_edges([{:a, :b}, {:b, :c}]) ...> match?(%{type: :directed, num_vertices: 4, num_edges: 2}, Graph.info(g)) true """ @spec info(t) :: graph_info() def info(%__MODULE__{type: type} = g) do %{ type: type, num_edges: num_edges(g), num_vertices: num_vertices(g), size_in_bytes: Graph.Utils.sizeof(g) } end @doc """ Converts the given Graph to DOT format, which can then be converted to a number of other formats via Graphviz, e.g. `dot -Tpng out.dot > out.png`. If labels are set on a vertex, then those labels are used in the DOT output in place of the vertex itself. If no labels were set, then the vertex is stringified if it's a primitive type and inspected if it's not, in which case the inspect output will be quoted and used as the vertex label in the DOT file. Edge labels and weights will be shown as attributes on the edge definitions, otherwise they use the same labelling scheme for the involved vertices as described above. NOTE: Currently this function assumes graphs are directed graphs, but in the future it will support undirected graphs as well. NOTE: Currently this function assumes graphs are directed graphs, but in the future it will support undirected graphs as well. NOTE 2: To avoid to overwrite vertices with the same label, output is generated using the internal numeric ID as vertex label. Original label is expressed as `id[label="