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
Summary
Functions
Returns the number of leaf nodes (nodes with no outgoing branch edges).
Checks whether the mind map contains a directed cycle (considering all edges, including both branch and associative edges).
Returns the maximum depth of the mind map (number of branch edges from root to deepest leaf).
Returns all leaf node IDs (nodes with no outgoing branch edges).
Returns the maximum number of nodes at any single depth level.
Returns nodes that are not reachable from the root via branch edges.
Enumerates all root-to-leaf paths via branch edges.
Suggests candidate node pairs for merging based on neighborhood Jaccard similarity.
Returns a map of node type frequencies.
Validates mind map structural soundness.
Functions
@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)
2This analysis answers the question: "How many leaf ideas exist?"
@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)
falseThis analysis answers the question: "Is there a cycle in the graph?"
@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())
0This analysis answers the question: "How deep is the mind map?"
@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?"
@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)
3This analysis answers the question: "What is the widest level of the map?"
@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?"
@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?"
@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
@spec type_frequencies(Choreo.MindMap.t()) :: %{required(atom()) => 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?"
@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?"