Powered by AppSignal & Oban Pro

Getting Started with Yog

livebooks/guides/getting_started.livemd

Getting Started with Yog

Mix.install([
  {:yog_ex, "~> 0.98"},
  {:kino_vizjs, "~> 0.8.0"}
])

What is Yog?

যোগ (jōg) means connection, link, or union.

Yog is a comprehensive graph algorithm library for Elixir. It provides efficient, immutable data structures and a wide array of algorithms for network analysis, pathfinding, and community detection.

Creating your first Graph

In Yog, graphs are immutable structures. You can create an empty graph of a specific kind:

# Create a directed graph
g = Yog.directed()

# Create an undirected graph
u = Yog.undirected()

By default, a graph consists of:

  • kind: :directed or :undirected
  • nodes: A map of node_id => data
  • edges: Adjacency maps for fast lookups and O(1) transpose.

Growing the Graph

We build graphs by adding nodes and edges. Since Yog is functional and immutable, every operation returns a new graph.

Adding Nodes

Nodes can have any term as an ID and any term as data.

g = 
  Yog.directed()
  |> Yog.add_node(1, %{label: "Start"})
  |> Yog.add_node(2, %{label: "End"})
  |> Yog.add_nodes_from([3, 4, 5]) # Adding multiple nodes with nil data

# Let's visualize it
Kino.VizJS.render(Yog.Render.DOT.to_dot(g), height: "100px")

Adding Edges

Edges connect nodes. In Yog, we provide several ways to add edges, depending on whether you want to ensure the nodes exist or handle missing nodes.

# 1. add_edge! - Raises if nodes don't exist
g = g |> Yog.add_edge!(1, 2, 10)

# 2. add_edge_ensure - Automatically creates nodes if they are missing
g = g |> Yog.add_edge_ensure(2, 3, 5, %{label: "Auto-created"})

# 3. add_simple_edge - Adds an edge with weight 1
g = g |> Yog.add_simple_edge!(3, 1)

# Visualize the resulting graph
Kino.VizJS.render(Yog.Render.DOT.to_dot(g))

Live Builder

For incremental construction, use the Live builder. It tracks pending changes and syncs them efficiently.

builder =
  Yog.Builder.Live.new()
  |> Yog.Builder.Live.add_edge("A", "B", 10)
  |> Yog.Builder.Live.add_edge("B", "C", 5)

{builder, g_live} = Yog.Builder.Live.sync(builder, Yog.directed())
IO.puts("Live-built graph has #{Yog.node_count(g_live)} nodes and #{Yog.edge_count(g_live)} edges")

# Add more edges incrementally
builder = Yog.Builder.Live.add_edge(builder, "C", "D", 3)
{_builder, g_live} = Yog.Builder.Live.sync(builder, g_live)
IO.puts("After incremental sync: #{Yog.node_count(g_live)} nodes, #{Yog.edge_count(g_live)} edges")

Examining the Graph

You can query the graph’s structure using functions in the main Yog module or Yog.Model.

IO.puts "Nodes: #{Yog.node_count(g)}"
IO.puts "Edges: #{Yog.edge_count(g)}"

# Get successors of node 2
IO.inspect(Yog.successors(g, 2), label: "Successors of 2")

# Get neighbors regardless of direction
IO.inspect(Yog.neighbors(g, 3), label: "Neighbors of 3")

# Check if the graph is cyclic
IO.puts("Is cyclic? #{Yog.cyclic?(g)}")

Transformations

Yog excels at functional transformations. You can map or filter nodes and edges to create new graph versions.

# Double all edge weights
high_weight_graph = Yog.Transform.map_edges(g, fn weight -> 
  if is_number(weight), do: weight * 2, else: weight 
end)

# Filter for nodes with numeric IDs
numeric_only = Yog.Transform.filter_nodes_indexed(g, fn id, _ -> is_integer(id) end)

Comprehensive Algorithms

Yog comes with 60+ algorithms out of the box.

Pathfinding

# Find the shortest path from 1 to 3
case Yog.Pathfinding.shortest_path(g, 1, 3) do
  {:ok, path} -> 
    IO.puts "Found path with weight: #{path.weight}"
    IO.inspect(path.nodes, label: "Path")
  :error -> 
    IO.puts "No path found"
end

Community Detection

# Generate a graph with communities and detect them
sbm = Yog.Generator.Random.sbm([8, 8, 8], [[0.7, 0.1, 0.1], [0.1, 0.7, 0.1], [0.1, 0.1, 0.7]])
result = Yog.Community.Louvain.detect(sbm)

IO.puts("Detected #{result.num_communities} communities")
IO.inspect(result.assignments, label: "Node -> Community")

# Visualize with community colors
opts = Yog.Render.DOT.community_to_options(result)
Kino.VizJS.render(Yog.Render.DOT.to_dot(sbm, opts))

Visualization Formats

Yog supports both Graphviz DOT and Mermaid.js output.

# Graphviz DOT (rich, customizable)
dot = Yog.Render.DOT.to_dot(g, Yog.Render.DOT.theme(:dark))
Kino.VizJS.render(dot)

# Mermaid.js (great for Markdown docs)
mermaid = Yog.Render.Mermaid.to_mermaid(g, Yog.Render.Mermaid.theme(:minimal))
IO.puts(mermaid)

Serialization

You can easily import/export graphs in various formats like GraphML, JSON, GDF, Graph6, or DOT.

xml = """
1.0UTF-8

  
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
  

"""

# Let's load the graph
{:ok, graph} = Yog.IO.GraphML.deserialize(xml)

# Export to GDF (GUESS format)
gdf = Yog.IO.GDF.serialize(graph)
IO.puts String.slice(gdf, 0, 300) <> "..."

Next Steps

Explore the Algorithm Catalog to see everything Yog can do!