Multigraph with support for parallel edges.
A multigraph allows multiple edges between the same pair of nodes, which is useful for modeling real-world networks like transportation systems (multiple routes between cities) or social networks (multiple types of relationships).
Fields
kind- Either:directedor:undirectednodes- Map from node ID to node dataedges- Map from{from, to}to list of edges (parallel edge support)next_edge_id- Counter for generating unique edge IDs
Edge Storage
Each edge in the edges map is stored as a tuple:
{edge_id, weight} where edge_id is a unique identifier.
Examples
iex> multi = Yog.Multi.Graph.new(:undirected)
iex> multi.kind
:undirected
iex> multi.next_edge_id
0
Summary
Functions
Adds an edge to the multigraph.
Adds a node to the multigraph.
Counts the number of parallel edges between two nodes.
Backward compatibility: convert from legacy map format.
Gets all parallel edges between two nodes.
Creates a new empty multigraph.
Returns the number of nodes in the multigraph.
Removes a specific edge by its edge ID.
Convert to legacy map format.
Converts the multigraph to a simple graph.
Converts to a simple graph, aggregating parallel edges using a combining function.
Returns the total number of edges (including parallel edges).
Types
@type edge_id() :: non_neg_integer()
@type edge_key() :: {Yog.Model.node_id(), Yog.Model.node_id()}
@type t() :: %Yog.Multi.Graph{ edges: %{required(edge_key()) => [edge_data()]}, kind: :directed | :undirected, next_edge_id: non_neg_integer(), nodes: %{required(Yog.Model.node_id()) => term()} }
Functions
@spec add_edge(t(), Yog.Model.node_id(), Yog.Model.node_id(), number()) :: {:ok, t(), edge_id()} | {:error, term()}
Adds an edge to the multigraph.
Returns {:ok, updated_multi, edge_id} on success.
Returns {:error, reason} if nodes don't exist.
Parallel edges are allowed - multiple edges can exist between the same pair of nodes.
Examples
iex> multi = Yog.Multi.Graph.new(:undirected)
iex> multi = Yog.Multi.Graph.add_node(multi, 1, nil)
iex> multi = Yog.Multi.Graph.add_node(multi, 2, nil)
iex> {:ok, _multi, edge_id} = Yog.Multi.Graph.add_edge(multi, 1, 2, 10)
iex> is_integer(edge_id)
true
@spec add_node(t(), Yog.Model.node_id(), term()) :: t()
Adds a node to the multigraph.
If the node already exists, updates its data.
Examples
iex> multi = Yog.Multi.Graph.new(:undirected)
iex> multi = Yog.Multi.Graph.add_node(multi, 1, "node_data")
iex> Map.has_key?(multi.nodes, 1)
true
@spec edge_count(t(), Yog.Model.node_id(), Yog.Model.node_id()) :: non_neg_integer()
Counts the number of parallel edges between two nodes.
Examples
iex> multi = Yog.Multi.Graph.new(:undirected)
iex> multi = Yog.Multi.Graph.add_node(multi, 1, nil)
iex> multi = Yog.Multi.Graph.add_node(multi, 2, nil)
iex> {:ok, multi, _} = Yog.Multi.Graph.add_edge(multi, 1, 2, 10)
iex> count = Yog.Multi.Graph.edge_count(multi, 1, 2)
iex> count >= 1
true
Backward compatibility: convert from legacy map format.
@spec get_edges(t(), Yog.Model.node_id(), Yog.Model.node_id()) :: [edge_data()]
Gets all parallel edges between two nodes.
Returns a list of {edge_id, weight} tuples.
Examples
iex> multi = Yog.Multi.Graph.new(:undirected)
iex> multi = Yog.Multi.Graph.add_node(multi, 1, nil)
iex> multi = Yog.Multi.Graph.add_node(multi, 2, nil)
iex> {:ok, multi, _} = Yog.Multi.Graph.add_edge(multi, 1, 2, 10)
iex> edges = Yog.Multi.Graph.get_edges(multi, 1, 2)
iex> length(edges) >= 1
true
@spec new(:directed | :undirected) :: t()
Creates a new empty multigraph.
Examples
iex> multi = Yog.Multi.Graph.new(:directed)
iex> multi.kind
:directed
iex> Enum.count(multi.nodes)
0
iex> multi = Yog.Multi.Graph.new(:undirected)
iex> multi.kind
:undirected
@spec node_count(t()) :: non_neg_integer()
Returns the number of nodes in the multigraph.
Examples
iex> multi = Yog.Multi.Graph.new(:undirected)
iex> multi = Yog.Multi.Graph.add_node(multi, 1, nil)
iex> multi = Yog.Multi.Graph.add_node(multi, 2, nil)
iex> count = Yog.Multi.Graph.node_count(multi)
iex> count
2
@spec remove_edge(t(), Yog.Model.node_id(), Yog.Model.node_id(), edge_id()) :: t()
Removes a specific edge by its edge ID.
Examples
iex> multi = Yog.Multi.Graph.new(:undirected)
iex> multi = Yog.Multi.Graph.add_node(multi, 1, nil)
iex> multi = Yog.Multi.Graph.add_node(multi, 2, nil)
iex> {:ok, multi, edge_id} = Yog.Multi.Graph.add_edge(multi, 1, 2, 10)
iex> multi = Yog.Multi.Graph.remove_edge(multi, 1, 2, edge_id)
iex> Yog.Multi.Graph.edge_count(multi, 1, 2)
0
Convert to legacy map format.
Converts the multigraph to a simple graph.
When there are parallel edges, only the first edge is kept. This allows compatibility with algorithms that don't support parallel edges.
Examples
iex> multi = Yog.Multi.Graph.new(:undirected)
iex> multi = Yog.Multi.Graph.add_node(multi, 1, nil)
iex> multi = Yog.Multi.Graph.add_node(multi, 2, nil)
iex> {:ok, multi, _} = Yog.Multi.Graph.add_edge(multi, 1, 2, 10)
iex> {:ok, multi, _} = Yog.Multi.Graph.add_edge(multi, 1, 2, 20)
iex> _simple = Yog.Multi.Graph.to_simple_graph(multi)
iex> # simple graph will have only one edge between 1 and 2
Converts to a simple graph, aggregating parallel edges using a combining function.
Examples
iex> multi = Yog.Multi.Graph.new(:undirected)
iex> multi = Yog.Multi.Graph.add_node(multi, 1, nil)
iex> multi = Yog.Multi.Graph.add_node(multi, 2, nil)
iex> {:ok, multi, _} = Yog.Multi.Graph.add_edge(multi, 1, 2, 10)
iex> {:ok, multi, _} = Yog.Multi.Graph.add_edge(multi, 1, 2, 20)
iex> simple = Yog.Multi.Graph.to_simple_graph_with(multi, &Kernel.+/2)
iex> Yog.Model.has_edge?(simple, 1, 2)
true
@spec total_edge_count(t()) :: non_neg_integer()
Returns the total number of edges (including parallel edges).
Examples
iex> multi = Yog.Multi.Graph.new(:undirected)
iex> multi = Yog.Multi.Graph.add_node(multi, 1, nil)
iex> multi = Yog.Multi.Graph.add_node(multi, 2, nil)
iex> {:ok, multi, _} = Yog.Multi.Graph.add_edge(multi, 1, 2, 10)
iex> total = Yog.Multi.Graph.total_edge_count(multi)
iex> total >= 1
true