Graph-algorithm wrappers for Choreo architecture diagrams.
These functions adapt the generic algorithms from Yog to the
domain of system architecture.
Semiring support
Functions that deal with path weights (e.g. shortest_path/4,
mst/2) accept semiring options so you can define "shortest" in
terms of cost, latency, or any custom metric:
# Shortest by cost (default — numeric addition)
Choreo.Analysis.shortest_path(system, :api, :db)
# Shortest by latency stored in edge meta
Choreo.Analysis.shortest_path(system, :api, :db,
zero: 0,
add: &Kernel.+/2,
compare: &Yog.Utils.compare/2
)Domain-specific analysis
single_points_of_failure/1— articulation points and bridge edgesimpact_analysis/2— what breaks if a node goes downcentrality/2— most coupled / critical servicesisolated_nodes/1— orphan services with no connectionsvalidate/1— structural health check
Summary
Functions
Ranks nodes by centrality — identifying the most critical services.
Detects cycles in the system.
Returns true if the system is a Directed Acyclic Graph (DAG).
This analysis answers the question: "Can I safely order my services linearly?"
Returns all nodes that are transitively affected if target goes down.
Returns nodes with no connections (zero in-degree and out-degree).
Computes a Minimum Spanning Tree (MST) of the system.
Finds the shortest (cheapest) path between two services.
Identifies single points of failure in the architecture.
Returns the strongly-connected components of the system.
Returns a topological ordering of the system nodes.
Validates the system architecture and returns a list of issues.
Functions
@spec centrality( Choreo.t(), keyword() ) :: [{Yog.node_id(), float()}]
Ranks nodes by centrality — identifying the most critical services.
## Options
* `:measure` — centrality algorithm to use:
- `:degree` (default) — simple connectivity count
- `:betweenness` — bridge/gatekeeper detection
- `:closeness` — distance-based importance
- `:pagerank` — link-quality importance (directed graphs)
* `:limit` — return only the top N results
* `:mode` — for degree centrality: `:in_degree`, `:out_degree`,
or `:total_degree` (default)
* Semiring options (`:zero`, `:add`, `:compare`, `:to_float`)
for betweenness and closeness Returns a list of {node_id, score} tuples sorted by score descending.
## Examples
iex> system =
...> Choreo.new()
...> |> Choreo.add_service(:api)
...> |> Choreo.add_service(:auth)
...> |> Choreo.add_database(:db)
...> |> Choreo.connect(:api, :auth)
...> |> Choreo.connect(:auth, :db)
iex> [{top_node, _score} | _rest] = Choreo.Analysis.centrality(system)
iex> top_node
:authThis analysis answers the question: "Which services are the most critical connectors?"
Detects cycles in the system.
Returns true if the system contains at least one cycle.
This analysis answers the question: "Does my architecture contain a feedback loop?"
Returns true if the system is a Directed Acyclic Graph (DAG).
This analysis answers the question: "Can I safely order my services linearly?"
@spec impact_analysis(Choreo.t(), Yog.node_id()) :: [Yog.node_id()]
Returns all nodes that are transitively affected if target goes down.
Uses BFS on the transposed graph: if A → B → C and target is B,
then A depends (transitively) on B, so A is affected.
## Examples
iex> system =
...> Choreo.new()
...> |> Choreo.add_service(:api)
...> |> Choreo.add_service(:auth)
...> |> Choreo.add_database(:db)
...> |> Choreo.connect(:api, :auth)
...> |> Choreo.connect(:auth, :db)
iex> Choreo.Analysis.impact_analysis(system, :db) |> Enum.sort()
[:api, :auth]This analysis answers the question: "What breaks if this service goes down?"
@spec isolated_nodes(Choreo.t()) :: [Yog.node_id()]
Returns nodes with no connections (zero in-degree and out-degree).
Isolated services in an architecture diagram are usually a mistake — either a missing connection or an orphaned component.
## Examples
iex> system =
...> Choreo.new()
...> |> Choreo.add_service(:connected)
...> |> Choreo.add_service(:orphan)
...> |> Choreo.connect(:connected, :connected)
iex> Choreo.Analysis.isolated_nodes(system)
[:orphan]This analysis answers the question: "Which services have no connections at all?"
@spec mst( Choreo.t(), keyword() ) :: {:ok, Yog.MST.Result.t()} | {:error, atom() | String.t()}
Computes a Minimum Spanning Tree (MST) of the system.
The MST is calculated on an undirected copy of the graph using
Kruskal's algorithm. Edge costs are the weights given to connect/4.
Returns {:ok, Yog.MST.Result.t()} or {:error, reason}.
## Options
* `:algorithm` - `:kruskal` (default), `:prim`, `:boruvka`
* `:compare` - custom comparison function for edge weights## Examples
iex> system =
...> Choreo.new(directed: false)
...> |> Choreo.add_database(:db)
...> |> Choreo.add_service(:api)
...> |> Choreo.add_cache(:cache)
...> |> Choreo.connect(:api, :db, cost: 10)
...> |> Choreo.connect(:api, :cache, cost: 5)
...> |> Choreo.connect(:db, :cache, cost: 20)
iex> {:ok, mst} = Choreo.Analysis.mst(system)
iex> mst.total_weight
15This analysis answers the question: "What is the cheapest way to connect all services?"
@spec shortest_path(Choreo.t(), Yog.node_id(), Yog.node_id(), keyword()) :: {:ok, map()} | :error
Finds the shortest (cheapest) path between two services.
Supports semiring-parameterised weights: pass :zero, :add, and
:compare to define "shortest" in terms of cost, latency, or any
custom metric.
Returns {:ok, path} where path has .nodes and .weight,
or :error if no path exists.
## Options
* `:zero` — identity element for the weight type (default: `0`)
* `:add` — function to combine two weights (default: `&Kernel.+/2`)
* `:compare` — function returning `:lt`, `:eq`, `:gt` (default: `&Yog.Utils.compare/2`)## Examples
iex> system =
...> Choreo.new()
...> |> Choreo.add_service(:api)
...> |> Choreo.add_service(:auth)
...> |> Choreo.add_database(:db)
...> |> Choreo.connect(:api, :auth, cost: 5)
...> |> Choreo.connect(:auth, :db, cost: 10)
iex> {:ok, path} = Choreo.Analysis.shortest_path(system, :api, :db)
iex> path.nodes
[:api, :auth, :db]
iex> path.weight
15This analysis answers the question: "What is the fastest or cheapest route between two services?"
@spec single_points_of_failure(Choreo.t()) :: %{ nodes: [Yog.node_id()], edges: [{Yog.node_id(), Yog.node_id()}] }
Identifies single points of failure in the architecture.
Returns a map with:
* `:nodes` — articulation points: services whose removal would
partition the system into disconnected components
* `:edges` — bridge edges: connections whose removal would split
the systemUses Tarjan's algorithm on an undirected view of the graph.
## Examples
iex> system =
...> Choreo.new()
...> |> Choreo.add_service(:api)
...> |> Choreo.add_service(:auth)
...> |> Choreo.add_database(:db)
...> |> Choreo.connect(:api, :auth)
...> |> Choreo.connect(:auth, :db)
iex> result = Choreo.Analysis.single_points_of_failure(system)
iex> result.nodes
[:auth]
iex> result.edges
[{:api, :auth}, {:auth, :db}]This analysis answers the question: "Which services or links would take down the whole system if they failed?"
@spec strongly_connected_components(Choreo.t()) :: [[Yog.node_id()]]
Returns the strongly-connected components of the system.
Each component is a list of node IDs that are mutually reachable. Single-node components indicate nodes that are not part of a cycle.
## Examples
components = Choreo.Analysis.strongly_connected_components(system)This analysis answers the question: "Which services are mutually dependent?"
@spec topological_sort(Choreo.t()) :: {:ok, [Yog.node_id()]} | {:error, String.t()}
Returns a topological ordering of the system nodes.
This is useful for determining execution order in data-flow
pipelines. The system graph must be a DAG; if cycles exist,
{:error, reason} is returned.
## Examples
iex> system =
...> Choreo.new()
...> |> Choreo.add_service(:ingest)
...> |> Choreo.add_service(:transform)
...> |> Choreo.add_service(:store)
...> |> Choreo.add_dataflow(:ingest, :transform)
...> |> Choreo.add_dataflow(:transform, :store)
iex> {:ok, order} = Choreo.Analysis.topological_sort(system)
iex> order
[:ingest, :transform, :store]This analysis answers the question: "In what order should I deploy or initialise services?"
Validates the system architecture and returns a list of issues.
Checks for:
* isolated nodes (no connections)
* single points of failure (articulation points)
* cycles in a directed system
* bridge edges (single connection between components) Returns a list of {severity, message} tuples.
## Examples
iex> system =
...> Choreo.new()
...> |> Choreo.add_service(:api)
...> |> Choreo.add_service(:auth)
...> |> Choreo.add_database(:db)
...> |> Choreo.connect(:api, :auth)
...> |> Choreo.connect(:auth, :db)
iex> [{_severity, msg} | _rest] = Choreo.Analysis.validate(system)
iex> String.contains?(msg, "Bridge edges")
trueThis analysis answers the question: "Is my architecture structurally sound?"