Skip to main content

weavatrix_graph/algo/
bellman.rs

1use super::measure::Measure;
2use crate::Vec;
3use crate::{GraphError, IndexGraphView, Result, String};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct SignedPath<Node, Cost = i64> {
7    nodes: Vec<Node>,
8    total_cost: Cost,
9}
10
11impl<Node, Cost> SignedPath<Node, Cost> {
12    pub(super) fn from_parts(nodes: Vec<Node>, total_cost: Cost) -> Self {
13        Self { nodes, total_cost }
14    }
15
16    #[must_use]
17    pub fn nodes(&self) -> &[Node] {
18        &self.nodes
19    }
20
21    #[must_use]
22    pub const fn total_cost(&self) -> Cost
23    where
24        Cost: Copy,
25    {
26        self.total_cost
27    }
28
29    #[must_use]
30    pub fn into_nodes(self) -> Vec<Node> {
31        self.nodes
32    }
33}
34
35#[derive(Debug, Clone)]
36pub struct BellmanFord<Node, Cost = i64> {
37    source: Node,
38    nodes: Vec<Node>,
39    nodes_by_slot: Vec<Option<Node>>,
40    distances: Vec<Cost>,
41    reachable: Vec<bool>,
42    predecessors: Vec<Option<usize>>,
43    node_slot: fn(Node) -> usize,
44}
45
46impl<Node, Cost> BellmanFord<Node, Cost>
47where
48    Node: Copy + Eq,
49    Cost: Copy,
50{
51    pub(super) fn from_parts(
52        source: Node,
53        nodes: Vec<Node>,
54        nodes_by_slot: Vec<Option<Node>>,
55        distances: Vec<Cost>,
56        reachable: Vec<bool>,
57        predecessors: Vec<Option<usize>>,
58        node_slot: fn(Node) -> usize,
59    ) -> Self {
60        Self {
61            source,
62            nodes,
63            nodes_by_slot,
64            distances,
65            reachable,
66            predecessors,
67            node_slot,
68        }
69    }
70
71    #[must_use]
72    pub const fn source(&self) -> Node {
73        self.source
74    }
75
76    #[must_use]
77    pub fn nodes(&self) -> &[Node] {
78        &self.nodes
79    }
80
81    #[must_use]
82    pub fn distance_to(&self, node: Node) -> Option<Cost> {
83        let slot = (self.node_slot)(node);
84        self.nodes_by_slot
85            .get(slot)
86            .is_some_and(|stored| *stored == Some(node))
87            .then(|| self.reachable[slot])
88            .filter(|reachable| *reachable)
89            .map(|_| self.distances[slot])
90    }
91
92    #[must_use]
93    pub fn predecessor(&self, node: Node) -> Option<Node> {
94        let slot = (self.node_slot)(node);
95        self.nodes_by_slot
96            .get(slot)
97            .is_some_and(|stored| *stored == Some(node))
98            .then(|| self.predecessors[slot])
99            .flatten()
100            .and_then(|predecessor| self.nodes_by_slot[predecessor])
101    }
102
103    #[must_use]
104    pub fn path_to(&self, target: Node) -> Option<SignedPath<Node, Cost>> {
105        let total_cost = self.distance_to(target)?;
106        let mut nodes = vec![target];
107        let mut cursor = target;
108        while cursor != self.source {
109            cursor = self.predecessor(cursor)?;
110            nodes.push(cursor);
111            if nodes.len() > self.nodes.len() {
112                return None;
113            }
114        }
115        nodes.reverse();
116        Some(SignedPath { nodes, total_cost })
117    }
118}
119
120/// Computes signed shortest paths from `source`.
121///
122/// # Errors
123///
124/// Returns an error for arithmetic overflow or a reachable negative cycle.
125pub fn bellman_ford<G, F>(
126    graph: &G,
127    source: G::Node,
128    edge_cost: F,
129) -> Result<Option<BellmanFord<G::Node>>>
130where
131    G: IndexGraphView,
132    F: Fn(G::Edge) -> i64,
133{
134    bellman_ford_filtered(graph, source, |edge| Some(edge_cost(edge)))
135}
136
137/// Computes signed shortest paths using only edges with a returned cost.
138///
139/// # Errors
140///
141/// Returns an error for arithmetic overflow or a reachable negative cycle.
142pub fn bellman_ford_filtered<G, F>(
143    graph: &G,
144    source: G::Node,
145    edge_cost: F,
146) -> Result<Option<BellmanFord<G::Node>>>
147where
148    G: IndexGraphView,
149    F: Fn(G::Edge) -> Option<i64>,
150{
151    bellman_ford_measure_filtered(graph, source, edge_cost)
152}
153
154/// Computes shortest paths with an arbitrary signed or unsigned measure.
155///
156/// # Errors
157///
158/// Returns an error for non-finite costs, arithmetic overflow, or a reachable
159/// negative cycle.
160pub fn bellman_ford_measure<G, Cost, F>(
161    graph: &G,
162    source: G::Node,
163    edge_cost: F,
164) -> Result<Option<BellmanFord<G::Node, Cost>>>
165where
166    G: IndexGraphView,
167    Cost: Measure,
168    F: Fn(G::Edge) -> Cost,
169{
170    bellman_ford_measure_filtered(graph, source, |edge| Some(edge_cost(edge)))
171}
172
173/// Computes filtered shortest paths with an arbitrary checked measure.
174///
175/// # Errors
176///
177/// Returns an error for non-finite costs, arithmetic overflow, or a reachable
178/// negative cycle.
179pub fn bellman_ford_measure_filtered<G, Cost, F>(
180    graph: &G,
181    source: G::Node,
182    edge_cost: F,
183) -> Result<Option<BellmanFord<G::Node, Cost>>>
184where
185    G: IndexGraphView,
186    Cost: Measure,
187    F: Fn(G::Edge) -> Option<Cost>,
188{
189    if !graph.contains_node(source) {
190        return Ok(None);
191    }
192    let mut nodes_by_slot = vec![None; graph.node_bound()];
193    let nodes = graph.node_indices().collect::<Vec<_>>();
194    for &node in &nodes {
195        nodes_by_slot[G::node_slot(node)] = Some(node);
196    }
197    let mut edges = Vec::with_capacity(graph.edge_count());
198    for (edge, endpoints) in graph.edge_references() {
199        if let Some(weight) = edge_cost(edge) {
200            if !weight.is_valid() {
201                return Err(GraphError::InvalidAlgorithmParameter {
202                    algorithm: "Bellman-Ford",
203                    parameter: "edge_cost",
204                    value: String::from("must be finite and totally ordered"),
205                });
206            }
207            edges.push((
208                G::node_slot(endpoints.source()),
209                G::node_slot(endpoints.target()),
210                weight,
211            ));
212        }
213    }
214    let mut distances = vec![Cost::zero(); graph.node_bound()];
215    let mut reachable = vec![false; graph.node_bound()];
216    let mut predecessors = vec![None; graph.node_bound()];
217    reachable[G::node_slot(source)] = true;
218    for _ in 1..nodes.len() {
219        if !relax_all(&edges, &mut distances, &mut reachable, &mut predecessors)? {
220            break;
221        }
222    }
223    reject_negative_cycle(&edges, &distances, &reachable)?;
224
225    Ok(Some(BellmanFord {
226        source,
227        nodes,
228        nodes_by_slot,
229        distances,
230        reachable,
231        predecessors,
232        node_slot: G::node_slot,
233    }))
234}
235
236fn relax_all<Cost: Measure>(
237    edges: &[(usize, usize, Cost)],
238    distances: &mut [Cost],
239    reachable: &mut [bool],
240    predecessors: &mut [Option<usize>],
241) -> Result<bool> {
242    let mut changed = false;
243    for &(source, target, weight) in edges {
244        if !reachable[source] {
245            continue;
246        }
247        let candidate =
248            distances[source]
249                .checked_add(weight)
250                .ok_or(GraphError::ArithmeticOverflow {
251                    operation: "Bellman-Ford edge relaxation",
252                })?;
253        if !reachable[target]
254            || candidate.compare(distances[target]) == Some(core::cmp::Ordering::Less)
255        {
256            distances[target] = candidate;
257            reachable[target] = true;
258            predecessors[target] = Some(source);
259            changed = true;
260        }
261    }
262    Ok(changed)
263}
264
265fn reject_negative_cycle<Cost: Measure>(
266    edges: &[(usize, usize, Cost)],
267    distances: &[Cost],
268    reachable: &[bool],
269) -> Result<()> {
270    for &(source, target, weight) in edges {
271        if !reachable[source] {
272            continue;
273        }
274        let candidate =
275            distances[source]
276                .checked_add(weight)
277                .ok_or(GraphError::ArithmeticOverflow {
278                    operation: "Bellman-Ford cycle check",
279                })?;
280        if reachable[target]
281            && candidate.compare(distances[target]) == Some(core::cmp::Ordering::Less)
282        {
283            return Err(GraphError::NegativeCycle {
284                algorithm: "Bellman-Ford",
285            });
286        }
287    }
288    Ok(())
289}