Current section

Files

Jump to
yog src yog@pathfinding@matrix.erl
Raw

src/yog@pathfinding@matrix.erl

-module(yog@pathfinding@matrix).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/yog/pathfinding/matrix.gleam").
-export([distance_matrix/5]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Optimized distance matrix computation for subsets of nodes.\n"
"\n"
" This module provides an auto-selecting algorithm for computing shortest path\n"
" distances between specified \"points of interest\" (POIs) in a graph. It chooses\n"
" between Floyd-Warshall and multiple Dijkstra runs based on POI density.\n"
"\n"
" ## Algorithm Selection\n"
"\n"
" | Algorithm | When Selected | Complexity |\n"
" |-----------|---------------|------------|\n"
" | [Floyd-Warshall](https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm) | Many POIs (> V/3) | O(V³) then filter |\n"
" | [Dijkstra](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm) × P | Few POIs (≤ V/3) | O(P × (V + E) log V) |\n"
"\n"
" ## Heuristic\n"
"\n"
" The crossover point is P > V/3 where P is the number of points of interest:\n"
" - **Dense POIs**: Floyd-Warshall computes all-pairs once, then filter\n"
" - **Sparse POIs**: Run Dijkstra from each POI individually\n"
"\n"
" This heuristic balances the O(V³) Floyd-Warshall against the O(P(V+E) log V)\n"
" cost of multiple Dijkstra runs.\n"
"\n"
" ## Use Cases\n"
"\n"
" - **Game AI**: Pathfinding between key locations (not all nodes)\n"
" - **Logistics**: Distance matrix for delivery stops\n"
" - **Facility location**: Distances between candidate sites\n"
" - **Network analysis**: Selected node pairwise distances\n"
"\n"
" ## Example\n"
"\n"
" ```gleam\n"
" // Compute distances only between important waypoints\n"
" let pois = [start, waypoint_a, waypoint_b, goal]\n"
" let distances = matrix.distance_matrix(\n"
" in: graph,\n"
" between: pois,\n"
" with_zero: 0,\n"
" with_add: int.add,\n"
" with_compare: int.compare,\n"
" )\n"
" // Result contains only 4×4 = 16 distances, not full V×V matrix\n"
" ```\n"
"\n"
" ## References\n"
"\n"
" - See `yog/pathfinding/floyd_warshall` for all-pairs algorithm details\n"
" - See `yog/pathfinding/dijkstra` for single-source algorithm details\n"
).
-file("src/yog/pathfinding/matrix.gleam", 64).
?DOC(
" Computes shortest distances between all pairs of points of interest.\n"
"\n"
" Automatically chooses between Floyd-Warshall and multiple Dijkstra runs\n"
" based on the density of POIs relative to graph size.\n"
"\n"
" **Time Complexity:** O(V³) or O(P × (V + E) log V)\n"
).
-spec distance_matrix(
yog@model:graph(any(), XDN),
list(integer()),
XDN,
fun((XDN, XDN) -> XDN),
fun((XDN, XDN) -> gleam@order:order())
) -> {ok, gleam@dict:dict({integer(), integer()}, XDN)} | {error, nil}.
distance_matrix(Graph, Points_of_interest, Zero, Add, Compare) ->
Num_nodes = maps:size(erlang:element(3, Graph)),
Num_pois = erlang:length(Points_of_interest),
Poi_set = gleam@set:from_list(Points_of_interest),
case (Num_pois * 3) > Num_nodes of
true ->
case yog@pathfinding@floyd_warshall:floyd_warshall(
Graph,
Zero,
Add,
Compare
) of
{error, nil} ->
{error, nil};
{ok, All_distances} ->
Poi_distances = gleam@dict:filter(
All_distances,
fun(Key, _) ->
{From_node, To_node} = Key,
gleam@set:contains(Poi_set, From_node) andalso gleam@set:contains(
Poi_set,
To_node
)
end
),
{ok, Poi_distances}
end;
false ->
Result = gleam@list:fold(
Points_of_interest,
maps:new(),
fun(Acc, Source) ->
Distances = yog@pathfinding@dijkstra:single_source_distances(
Graph,
Source,
Zero,
Add,
Compare
),
gleam@list:fold(
Points_of_interest,
Acc,
fun(Acc2, Target) ->
case gleam_stdlib:map_get(Distances, Target) of
{ok, Dist} ->
gleam@dict:insert(
Acc2,
{Source, Target},
Dist
);
{error, nil} ->
Acc2
end
end
)
end
),
{ok, Result}
end.