Minimum cost flow algorithm using the Network Simplex method.
This module solves the minimum cost flow problem: find the cheapest way to send a given amount of flow through a network where edges have both capacities and costs per unit of flow.
Algorithm
The Network Simplex algorithm is a specialized primal simplex method for network flows. In practice, it runs extremely fast (polynomial-like time) and handles arbitrary flows/demands. Unlike pseudo-polynomial algorithms (like Successive Shortest Path), its performance is independent of the total flow capacity (F), making it suitable for large networks.
Key Concepts
- Minimum Cost Flow: Route flow to satisfy demands at minimum total cost
- Node Demands: Supply nodes (negative demand) and demand nodes (positive demand)
- Edge Costs: Price per unit of flow on each edge
- Reduced Costs: Modified costs ensuring optimality conditions
- Spanning Tree: Basis for the simplex method on networks
Implementation Notes
- Internal state is stored in maps keyed by integer indices (nodes and edges).
For very dense graphs, switching to Erlang
:arraycould reduce memory and lookup overhead, but it would be a sizeable refactor. - The pivot step uses
Enum.find_index/2to locate the entering and leaving edges inside the fundamental cycle. Each call is O(cycle length); building a position map would speed up hot paths on large instances. - The unboundedness test checks whether any original edge flow is at least
half of the artificial big-M cost (
faux_inf / 2). This 2x buffer is a defensive choice; NetworkX usesflow >= faux_infdirectly. - Zero-capacity edges are filtered out before solving. Self-loops are kept and treated as non-tree edges, so finite negative-cost self-loops are saturated when beneficial.
Summary
Types
A flow vector assigning an amount of flow to each edge.
Errors that can occur during minimum cost flow optimization.
Result of a successful minimum cost flow computation.
Functions
Solves the minimum cost flow problem using the Network Simplex algorithm.
Types
@type flow_map() :: [{Yog.node_id(), Yog.node_id(), integer()}]
A flow vector assigning an amount of flow to each edge.
Each tuple is {from_node, to_node, flow_amount}.
Note: the name flow_map is historical; the type is a list of flow tuples,
not a map.
@type min_cost_flow_error() ::
:infeasible | :unbalanced_demands | :unbounded | :timeout
Errors that can occur during minimum cost flow optimization.
:infeasible- The demands cannot be satisfied given the edge capacities:unbalanced_demands- The sum of all node demands does not equal 0:unbounded- The network contains a negative-cost cycle with infinite capacity:timeout- The algorithm did not converge within the allowed number of pivots
Result of a successful minimum cost flow computation.
Functions
@spec min_cost_flow(Yog.graph(), (any() -> integer()), (any() -> integer()), (any() -> integer())) :: {:ok, min_cost_flow_result()} | {:error, min_cost_flow_error()}
Solves the minimum cost flow problem using the Network Simplex algorithm.
Given a network with edge capacities and costs, and node demands/supplies, finds the flow assignment that satisfies all demands at minimum total cost.
Parameters
graph- The flow network (directed graph with edge data representing capacities)get_demand- Function(node_data) -> demandwhere negative = supply, positive = demandget_capacity- Function(edge_data) -> capacityget_cost- Function(edge_data) -> cost_per_unit
Note: These functions take the node/edge data stored in the graph, not node IDs.
Returns
{:ok, result}- Successful computation with%{cost: integer(), flow: flow_map()}{:error, :infeasible}- Demands cannot be satisfied{:error, :unbalanced_demands}- Total supply ≠ total demand{:error, :unbounded}- Negative cycle with infinite capacity found{:error, :timeout}- Maximum pivot limit exceeded
Examples
graph = Yog.directed()
|> Yog.add_node(1, {-20, nil})
|> Yog.add_node(2, {10, nil})
|> Yog.add_node(3, {10, nil})
|> Yog.add_edges([
{1, 2, {10, 3}},
{1, 3, {15, 2}},
{2, 3, {5, 1}}
])
get_demand = fn {d, _} -> d end
get_capacity = fn {c, _} -> c end
get_cost = fn {_, c} -> c end
{:ok, result} = Yog.Flow.NetworkSimplex.min_cost_flow(graph, get_demand, get_capacity, get_cost)