# `Yog.Multi`
[🔗](https://github.com/code-shoily/yog_ex/blob/v1.0.0/lib/yog/multi.ex#L1)

Unified facade for multigraph operations.

A multigraph allows multiple (parallel) edges between the same pair of nodes.
This module provides creation, modification, query, traversal, conversion, and algorithmic
analysis for multigraphs by delegating to specialized submodules (`Yog.Multi.Model`,
`Yog.Multi.Traversal`, `Yog.Multi.Eulerian`).

## Multigraph Representation & Edge IDs

Unlike simple graphs where edges are uniquely identified by their `{from, to}` endpoints,
multigraph edges are assigned unique sequential non-negative integer `EdgeId` identifiers
starting from `0`.

Adding an edge via `add_edge/4` returns a `{updated_graph, edge_id}` tuple:

    iex> multi = Yog.Multi.directed()
    iex> {multi, e1} = Yog.Multi.add_edge(multi, :a, :b, "route 1")
    iex> {multi, e2} = Yog.Multi.add_edge(multi, :a, :b, "route 2")
    iex> Yog.Multi.edge_count(multi, :a, :b)
    2
    iex> e1 != e2
    true

## Collapsing to Simple Graphs

Multigraphs can be collapsed into simple `Yog.Graph` structures using `to_simple_graph/1`,
`to_simple_graph/2`, `to_simple_graph_min_edges/1`, `to_simple_graph_max_edges/1`, or
`to_simple_graph_sum_edges/1`.

## Directed vs Undirected Multigraphs

- **Directed**: Edges have a specific direction (`from` -> `to`). In-degree and out-degree are separate.
- **Undirected**: Edges operate bidirectionally. Incident self-loops contribute 2 to node degree.

# `edge_id`

```elixir
@type edge_id() :: Yog.Multi.Model.edge_id()
```

# `graph`

```elixir
@type graph() :: Yog.Multi.Model.t()
```

# `add_edge`

Adds an edge to the multigraph, returning `{updated_graph, edge_id}`.

# `add_node`

Adds a node to the multigraph with optional custom payload.

# `all_edge_ids`

Returns all edge IDs in the graph.

# `all_edges`

Returns all edges as `[{edge_id, from, to, data}]` sorted by `edge_id`.

# `all_nodes`

Returns all node IDs in the multigraph.

# `bfs`

Performs a Breadth-First Search from source.

# `degree`

Returns the total degree of a node.

# `dfs`

Performs a Depth-First Search from source.

# `directed`

Creates a new empty directed multigraph.

# `edge`

Returns `{from, to, data}` for an `EdgeId`, or `nil` if not found.

# `edge_count`

Synonym for `size/1`.

# `edge_count`

Returns the number of parallel edges between `from` and `to`.

# `edge_data`

Returns edge data payload for an `EdgeId`, or `nil` if not found.

# `edges_between`

Returns all parallel edges between `from` and `to` as `[{edge_id, data}]`.

# `fetch_edge`

Fetches details for an `EdgeId` as `{:ok, {from, to, data}}` or `:error`.

# `fetch_node`

Fetches node data for a given node ID as `{:ok, data}` or `:error`.

# `find_eulerian_circuit`

Finds an Eulerian circuit using Hierholzer's algorithm.

# `find_eulerian_path`

Finds an Eulerian path using Hierholzer's algorithm.

# `fold_walk`

Folds over nodes during multigraph traversal with metadata.

# `has_cycle?`

```elixir
@spec has_cycle?(graph()) :: boolean()
```

Checks if the multigraph contains at least one cycle.

Collapses parallel edges internally before checking.

# `has_edge`

Checks if a specific `EdgeId` exists in the multigraph.

# `has_edge?`

Predicate synonym for `has_edge/2`.

# `has_edge_between`

Synonym for `has_edge_between?/3`.

# `has_edge_between?`

Checks if at least one edge exists between `from` and `to`.

# `has_eulerian_circuit?`

Checks if the multigraph has an Eulerian circuit.

# `has_eulerian_path?`

Checks if the multigraph has an Eulerian path.

# `has_node?`

Checks if a node ID exists in the multigraph.

# `in_degree`

Returns the in-degree of a node.

# `kind`

Synonym for `type/1`.

# `new`

Creates a new empty multigraph of the given type (`:directed` or `:undirected`).

# `node`

Returns data associated with a node, or `nil` if not found.

# `node_count`

Synonym for `order/1`.

# `node_data`

Synonym for `node/2`.

# `order`

Returns the number of nodes (order) in the multigraph.

# `out_degree`

Returns the out-degree of a node.

# `predecessors`

Returns all incoming edges to `id` as `[{from_node, edge_id, data}]`.

# `remove_edge`

Removes a single edge by its `EdgeId`.

# `remove_node`

Removes a node and all incident edges connected to it.

# `size`

Returns the total number of physical edges (size) in the multigraph.

# `successors`

Returns all outgoing edges from `id` as `[{to_node, edge_id, data}]`.

# `to_simple_graph`

Collapses the multigraph into a simple graph, keeping the earliest edge between each pair.

# `to_simple_graph`

Collapses the multigraph into a simple graph using a combining function.

# `to_simple_graph_max_edges`

Collapses parallel edges, keeping the maximum weight.

# `to_simple_graph_min_edges`

Collapses parallel edges, keeping the minimum weight.

# `to_simple_graph_sum_edges`

Collapses parallel edges, summing weights.

# `to_simple_graph_sum_edges`

Collapses parallel edges, combining weights with the provided function.

# `topological_sort`

```elixir
@spec topological_sort(graph()) :: {:ok, [Yog.node_id()]} | {:error, :contains_cycle}
```

Returns a topological ordering of nodes (directed multigraphs only).

Collapses parallel edges internally, then applies Kahn's algorithm.
Returns `{:ok, [node_id]}` or `{:error, :contains_cycle}`.

# `type`

Returns the type of the multigraph (`:directed` or `:undirected`).

# `undirected`

Creates a new empty undirected multigraph.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
