# `Choreo.MindMap.Analysis`
[🔗](https://github.com/code-shoily/choreo/blob/v0.14.1/lib/choreo/mind_map/analysis.ex#L1)

Analysis functions for `Choreo.MindMap`.

Provides algorithms that answer practical questions about a mind map:

  * How deep is the map? (max depth from root)
  * How broad is the map? (number of leaf nodes)
  * Which ideas are orphaned? (not reachable from root)
  * What are all root-to-leaf paths?
  * Is the map structurally sound?

## Further reading

  * [Mind map (Wikipedia)](https://en.wikipedia.org/wiki/Mind_map)

# `breadth`

```elixir
@spec breadth(Choreo.MindMap.t()) :: non_neg_integer()
```

Returns the number of leaf nodes (nodes with no outgoing branch edges).

## Examples

    iex> map = Choreo.MindMap.new()
    iex> map = map
    ...>   |> Choreo.MindMap.set_root(:a)
    ...>   |> Choreo.MindMap.add_topic(:b)
    ...>   |> Choreo.MindMap.add_topic(:c)
    ...>   |> Choreo.MindMap.branch(:a, :b)
    ...>   |> Choreo.MindMap.branch(:a, :c)
    iex> Choreo.MindMap.Analysis.breadth(map)
    2

This analysis answers the question: "How many leaf ideas exist?"

# `cyclic?`

```elixir
@spec cyclic?(Choreo.MindMap.t()) :: boolean()
```

Checks whether the mind map contains a directed cycle (considering all
edges, including both branch and associative edges).

## Examples

    iex> map = Choreo.MindMap.new()
    iex> map = map
    ...>   |> Choreo.MindMap.set_root(:a)
    ...>   |> Choreo.MindMap.add_topic(:b)
    ...>   |> Choreo.MindMap.add_topic(:c)
    ...>   |> Choreo.MindMap.branch(:a, :b)
    ...>   |> Choreo.MindMap.branch(:b, :c)
    ...>   |> Choreo.MindMap.branch(:c, :b)
    iex> Choreo.MindMap.Analysis.cyclic?(map)
    true

    iex> map = Choreo.MindMap.new()
    iex> map = map
    ...>   |> Choreo.MindMap.set_root(:a)
    ...>   |> Choreo.MindMap.add_topic(:b)
    ...>   |> Choreo.MindMap.branch(:a, :b)
    iex> Choreo.MindMap.Analysis.cyclic?(map)
    false

This analysis answers the question: "Is there a cycle in the graph?"

# `depth`

```elixir
@spec depth(Choreo.MindMap.t()) :: non_neg_integer()
```

Returns the maximum depth of the mind map (number of branch edges from
root to deepest leaf).

A single-node map has depth 0.

Raises if the map contains a cycle — use `cyclic?/1` to check first.

## Examples

    iex> map = Choreo.MindMap.new()
    iex> map = map
    ...>   |> Choreo.MindMap.set_root(:a, label: "a")
    ...>   |> Choreo.MindMap.add_topic(:b)
    ...>   |> Choreo.MindMap.add_subtopic(:c)
    ...>   |> Choreo.MindMap.branch(:a, :b)
    ...>   |> Choreo.MindMap.branch(:b, :c)
    iex> Choreo.MindMap.Analysis.depth(map)
    2
    iex> Choreo.MindMap.Analysis.depth(Choreo.MindMap.new())
    0

This analysis answers the question: "How deep is the mind map?"

# `leaves`

```elixir
@spec leaves(Choreo.MindMap.t()) :: [Yog.node_id()]
```

Returns all leaf node IDs (nodes with no outgoing branch edges).

## Examples

    iex> map = Choreo.MindMap.new()
    iex> map = map
    ...>   |> Choreo.MindMap.set_root(:a)
    ...>   |> Choreo.MindMap.add_topic(:b)
    ...>   |> Choreo.MindMap.add_topic(:c)
    ...>   |> Choreo.MindMap.branch(:a, :b)
    ...>   |> Choreo.MindMap.branch(:a, :c)
    iex> Enum.sort(Choreo.MindMap.Analysis.leaves(map))
    [:b, :c]

This analysis answers the question: "Which ideas have no children?"

# `max_width`

```elixir
@spec max_width(Choreo.MindMap.t()) :: non_neg_integer()
```

Returns the maximum number of nodes at any single depth level.

Each node is counted at most once, at its shallowest depth from the root.

## Examples

    iex> map = Choreo.MindMap.new()
    iex> map = map
    ...>   |> Choreo.MindMap.set_root(:a)
    ...>   |> Choreo.MindMap.add_topic(:b)
    ...>   |> Choreo.MindMap.add_topic(:c)
    ...>   |> Choreo.MindMap.add_topic(:d)
    ...>   |> Choreo.MindMap.branch(:a, :b)
    ...>   |> Choreo.MindMap.branch(:a, :c)
    ...>   |> Choreo.MindMap.branch(:a, :d)
    iex> Choreo.MindMap.Analysis.max_width(map)
    3

This analysis answers the question: "What is the widest level of the map?"

# `orphan_nodes`

```elixir
@spec orphan_nodes(Choreo.MindMap.t()) :: [Yog.node_id()]
```

Returns nodes that are not reachable from the root via branch edges.

## Examples

    iex> map = Choreo.MindMap.new()
    iex> map = map
    ...>   |> Choreo.MindMap.set_root(:a)
    ...>   |> Choreo.MindMap.add_topic(:b)
    ...>   |> Choreo.MindMap.add_topic(:c)
    ...>   |> Choreo.MindMap.branch(:a, :b)
    iex> Choreo.MindMap.Analysis.orphan_nodes(map)
    [:c]

This analysis answers the question: "Which ideas are disconnected from the root?"

# `paths`

```elixir
@spec paths(Choreo.MindMap.t()) :: [[Yog.node_id()]]
```

Enumerates all root-to-leaf paths via branch edges.

Each path is a list of node IDs from root to leaf.

Raises if the map contains a cycle — use `cyclic?/1` to check first.

## Examples

    iex> map = Choreo.MindMap.new()
    iex> map = map
    ...>   |> Choreo.MindMap.set_root(:a)
    ...>   |> Choreo.MindMap.add_topic(:b)
    ...>   |> Choreo.MindMap.add_subtopic(:c)
    ...>   |> Choreo.MindMap.branch(:a, :b)
    ...>   |> Choreo.MindMap.branch(:b, :c)
    iex> Choreo.MindMap.Analysis.paths(map)
    [[:a, :b, :c]]

This analysis answers the question: "What are all root-to-leaf paths?"

# `suggest_merges`

```elixir
@spec suggest_merges(Choreo.MindMap.t(), keyword()) :: [
  {Yog.node_id(), Yog.node_id(), float()}
]
```

Suggests candidate node pairs for merging based on neighborhood Jaccard similarity.

Two nodes are similar if they share a high percentage of neighbors (parents,
children, and associates).

Returns a list of tuples `[{node_a, node_b, similarity}]` sorted by similarity descending.

## Options

  * `:threshold` — minimum Jaccard similarity index to suggest (default: `0.5`)

## Examples

    iex> map = Choreo.MindMap.new()
    ...>   |> Choreo.MindMap.set_root(:root)
    ...>   |> Choreo.MindMap.add_topic(:a)
    ...>   |> Choreo.MindMap.add_topic(:b)
    ...>   |> Choreo.MindMap.add_subtopic(:c)
    ...>   |> Choreo.MindMap.branch(:root, :a)
    ...>   |> Choreo.MindMap.branch(:root, :b)
    ...>   |> Choreo.MindMap.branch(:a, :c)
    ...>   |> Choreo.MindMap.branch(:b, :c)
    iex> {:a, :b, 1.0} in Choreo.MindMap.Analysis.suggest_merges(map)
    true

# `type_frequencies`

```elixir
@spec type_frequencies(Choreo.MindMap.t()) :: %{required(atom()) =&gt; non_neg_integer()}
```

Returns a map of node type frequencies.

## Examples

    iex> map = Choreo.MindMap.new()
    iex> map = map
    ...>   |> Choreo.MindMap.set_root(:a)
    ...>   |> Choreo.MindMap.add_topic(:b)
    ...>   |> Choreo.MindMap.add_subtopic(:c)
    ...>   |> Choreo.MindMap.add_note(:d)
    iex> Choreo.MindMap.Analysis.type_frequencies(map)
    %{root: 1, topic: 1, subtopic: 1, note: 1}

This analysis answers the question: "What is the composition of the map?"

# `validate`

```elixir
@spec validate(Choreo.MindMap.t()) :: [{:error | :warning, String.t()}]
```

Validates mind map structural soundness.

Checks for:
  * missing root
  * cycles in the hierarchy
  * orphan nodes (not reachable from root)
  * nodes with multiple branch parents

Returns a list of `{severity, message}` tuples.

## Examples

    iex> map = Choreo.MindMap.new()
    iex> map = map
    ...>   |> Choreo.MindMap.set_root(:a)
    ...>   |> Choreo.MindMap.add_topic(:b)
    ...>   |> Choreo.MindMap.branch(:a, :b)
    iex> Choreo.MindMap.Analysis.validate(map)
    []

    iex> map = Choreo.MindMap.new()
    iex> Choreo.MindMap.Analysis.validate(map)
    [{:error, "Mind map has no root"}]

This analysis answers the question: "Is the mind map structurally sound?"

---

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