# Graph Properties & Optimization
```elixir
Mix.install([
{:yog_ex, path: "/home/mafinar/repos/elixir/yog_ex"},
{:kino_vizjs, "~> 0.8.0"}
])
```
## Introduction
Graph properties help us understand the mathematical nature of a network. Can it be drawn on a flat surface without edges crossing? Does it have a path that visits every edge exactly once? How can we connect all points with the minimum amount of cable?
`Yog` provides high-performance implementations for these classic graph theory problems.
## 🌲 Minimum Spanning Tree (MST)
An MST connects all nodes in a weighted graph with the minimum possible total edge weight and no cycles. This is the foundation of network design (e.g., laying fiber optic cables).
```elixir
# Create a weighted network
g = Yog.from_edges(:undirected, [
{:a, :b, 4}, {:a, :h, 8},
{:b, :c, 8}, {:b, :h, 11},
{:c, :d, 7}, {:c, :i, 2}, {:c, :f, 4},
{:d, :e, 9}, {:d, :f, 14},
{:e, :f, 10},
{:f, :g, 2},
{:g, :h, 1}, {:g, :i, 6},
{:h, :i, 7}
])
# Find the MST using Kruskal's algorithm
{:ok, result} = Yog.MST.kruskal(in: g)
IO.puts "MST Total Weight: #{result.total_weight}"
# # Visualize the MST (highlighting the edges)
mst_options = Yog.Render.DOT.mst_to_options(result, Yog.Render.DOT.theme(:minimal))
dot = Yog.Render.DOT.to_dot(g, mst_options)
Kino.VizJS.render(dot, height: "400px")
```
## 🌉 Eulerian Paths (Seven Bridges of Königsberg)
An **Eulerian Path** visits every edge exactly once. An **Eulerian Circuit** starts and ends at the same node.
```elixir
# The famous Seven Bridges of Königsberg graph
# (This multigraph cannot have an Eulerian path because 4 nodes have odd degrees)
konigsberg =
Yog.from_edges(:undirected, [
# Two bridges
{:north, :island, 1},
{:north, :island, 2},
# Two bridges
{:south, :island, 3},
{:south, :island, 4},
{:east, :island, 5},
{:north, :east, 6},
{:south, :east, 7}
])
case Yog.Property.Eulerian.eulerian_circuit(konigsberg) do
{:ok, _} ->
IO.puts("Eulerian circuit exists!")
{:error, _} ->
IO.puts("No Eulerian circuit possible. (Euler was right!)")
end
```
## 🎨 Graph Coloring
Graph coloring assigns a "color" to each node such that no two adjacent nodes share the same color. The goal is to use the minimum number of colors (**Chromatic Number**). This is often used for scheduling (nodes = tasks, edges = conflicts).
```elixir
g = Yog.Generator.Classic.petersen()
# Calculate a greedy coloring
{upper, colors} = Yog.Property.Coloring.coloring_greedy(g)
IO.puts "Used #{upper} colors."
# Visualize with colors!
color_values = ["#6366f1", "#10b981", "#f59e0b", "#ef4444", "#8b5cf6"]
node_styles = Map.new(colors, fn {node, color_idx} ->
color = Enum.at(color_values, color_idx)
{node, [color: color, style: "filled", fillcolor: color]}
end)
dot = Yog.Render.DOT.to_dot(g)
Kino.VizJS.render(dot)
```
## 🗺️ Planarity
A graph is **Planar** if it can be drawn in a plane without any edges crossing.
```elixir
# K5 (Complete graph with 5 nodes) is the smallest non-planar graph
k5 = Yog.Generator.Classic.complete(5)
if Yog.Property.Planarity.planar?(k5) do
IO.puts "K5 is planar"
else
IO.puts "K5 is NOT planar (as expected)"
end
```
## Summary
Exploring graph properties allows you to:
1. **Optimize**: Find the cheapest way to connect components (MST).
2. **Verify**: Check if a solution exists for routing problems (Eulerian).
3. **Schedule**: Resolve conflicts using Coloring.
4. **Visualize**: Ensure layout feasibility with Planarity.
Next, explore **DAGs** to see how to manage dependencies and workflows!