Yog.Pathfinding.Matrix (YogEx v0.70.0)

Copy Markdown View Source

Optimized distance matrix computation for subsets of nodes.

This module provides an auto-selecting algorithm for computing shortest path distances between specified "points of interest" (POIs) in a graph. It intelligently chooses between Floyd-Warshall, Johnson's, and multiple Dijkstra runs based on graph characteristics and POI density.

Algorithm Selection

With negative weights support (when subtract is provided):

AlgorithmWhen SelectedComplexity
Johnson'sSparse graphs (E < V²/4)O(V² log V + VE) then filter
Floyd-WarshallDense graphs (E ≥ V²/4)O(V³) then filter

Without negative weights (when subtract is nil):

AlgorithmWhen SelectedComplexity
Dijkstra × PFew POIs (P ≤ V/3)O(P × (V + E) log V)
Floyd-WarshallMany POIs (P > V/3)O(V³) then filter

Heuristics

For graphs with potential negative weights:

  • Johnson's algorithm is preferred for sparse graphs where E < V²/4
  • Floyd-Warshall is preferred for denser graphs

For non-negative weights only:

  • Multiple Dijkstra runs when P ≤ V/3 (few POIs)
  • Floyd-Warshall when P > V/3 (many POIs)

Use Cases

  • Game AI: Pathfinding between key locations (not all nodes)
  • Logistics: Distance matrix for delivery stops
  • Facility location: Distances between candidate sites
  • Network analysis: Selected node pairwise distances

Examples

# Compute distances only between important waypoints
iex> graph = Yog.undirected()
...> |> Yog.add_node(1, nil)
...> |> Yog.add_node(2, nil)
...> |> Yog.add_node(3, nil)
...> |> Yog.add_node(4, nil)
...> |> Yog.add_edge!(from: 1, to: 2, with: 4)
...> |> Yog.add_edge!(from: 2, to: 3, with: 1)
...> |> Yog.add_edge!(from: 3, to: 4, with: 2)
iex> pois = [1, 2, 4]  # Only care about these 3 nodes
iex> {:ok, distances} = Yog.Pathfinding.Matrix.distance_matrix(
...>   graph, pois, 0, &(&1 + &2), &Yog.Utils.compare/2
...> )
iex> # Distance from 1 to 4 should be 1->2->3->4 = 7
...> distances[{1, 4}]
7
iex> # Matrix only contains 3×3 = 9 entries
...> map_size(distances)
9

References

Summary

Types

Distance matrix: map from {from, to} tuple to distance.

Types

distance_matrix()

@type distance_matrix() :: %{required({Yog.node_id(), Yog.node_id()}) => any()}

Distance matrix: map from {from, to} tuple to distance.

Functions

distance_matrix(graph, points_of_interest, zero \\ 0, add \\ &Kernel.+/2, compare \\ &Yog.Utils.compare/2, subtract \\ nil)

@spec distance_matrix(
  Yog.graph(),
  [Yog.node_id()],
  any(),
  (any(), any() -> any()),
  (any(), any() -> :lt | :eq | :gt),
  (any(), any() -> any()) | nil
) :: {:ok, distance_matrix()} | {:error, :negative_cycle}

Computes shortest distances between all pairs of points of interest.

Automatically chooses the best algorithm based on:

  • Whether negative weights are possible (presence of subtract)
  • Graph sparsity (E relative to V²)
  • POI density (P relative to V)

Time Complexity: O(V³), O(V² log V + VE), or O(P × (V + E) log V)

Parameters

  • graph - The graph to analyze
  • points_of_interest - List of node IDs to compute distances between
  • zero - Identity element for addition
  • add - Function to add two weights
  • compare - Function to compare two weights
  • subtract - Optional subtraction function for negative weight support. If provided, enables Johnson's algorithm for sparse graphs with negative weights. If nil, assumes non-negative weights and may use Dijkstra.

Examples

# Non-negative weights only (uses Dijkstra or Floyd-Warshall)
iex> graph = Yog.undirected()
...> |> Yog.add_node(1, nil)
...> |> Yog.add_node(2, nil)
...> |> Yog.add_node(3, nil)
...> |> Yog.add_edge!(from: 1, to: 2, with: 4)
...> |> Yog.add_edge!(from: 2, to: 3, with: 1)
iex> {:ok, distances} = Yog.Pathfinding.Matrix.distance_matrix(
...>   graph, [1, 3], 0, &(&1 + &2), &Yog.Utils.compare/2
...> )
iex> distances[{1, 3}]
5

# Support negative weights (uses Johnson's or Floyd-Warshall)
iex> neg_graph = Yog.directed()
...> |> Yog.add_node(1, nil)
...> |> Yog.add_node(2, nil)
...> |> Yog.add_node(3, nil)
...> |> Yog.add_edge!(from: 1, to: 2, with: 4)
...> |> Yog.add_edge!(from: 2, to: 3, with: -2)
iex> {:ok, distances} = Yog.Pathfinding.Matrix.distance_matrix(
...>   neg_graph, [1, 3], 0, &(&1 + &2), &Yog.Utils.compare/2, &(&1 - &2)
...> )
iex> distances[{1, 3}]
2