Skip to main content

weavatrix_graph/algo/
bellman.rs

1use crate::{GraphError, IndexGraphView, Result};
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub struct SignedPath<Node> {
5    nodes: Vec<Node>,
6    total_cost: i64,
7}
8
9impl<Node> SignedPath<Node> {
10    #[must_use]
11    pub fn nodes(&self) -> &[Node] {
12        &self.nodes
13    }
14
15    #[must_use]
16    pub const fn total_cost(&self) -> i64 {
17        self.total_cost
18    }
19
20    #[must_use]
21    pub fn into_nodes(self) -> Vec<Node> {
22        self.nodes
23    }
24}
25
26#[derive(Debug, Clone)]
27pub struct BellmanFord<Node> {
28    source: Node,
29    nodes: Vec<Node>,
30    nodes_by_slot: Vec<Option<Node>>,
31    distances: Vec<i64>,
32    reachable: Vec<bool>,
33    predecessors: Vec<Option<usize>>,
34    node_slot: fn(Node) -> usize,
35}
36
37impl<Node> BellmanFord<Node>
38where
39    Node: Copy + Eq,
40{
41    #[must_use]
42    pub const fn source(&self) -> Node {
43        self.source
44    }
45
46    #[must_use]
47    pub fn nodes(&self) -> &[Node] {
48        &self.nodes
49    }
50
51    #[must_use]
52    pub fn distance_to(&self, node: Node) -> Option<i64> {
53        let slot = (self.node_slot)(node);
54        self.nodes_by_slot
55            .get(slot)
56            .is_some_and(|stored| *stored == Some(node))
57            .then(|| self.reachable[slot])
58            .filter(|reachable| *reachable)
59            .map(|_| self.distances[slot])
60    }
61
62    #[must_use]
63    pub fn predecessor(&self, node: Node) -> Option<Node> {
64        let slot = (self.node_slot)(node);
65        self.nodes_by_slot
66            .get(slot)
67            .is_some_and(|stored| *stored == Some(node))
68            .then(|| self.predecessors[slot])
69            .flatten()
70            .and_then(|predecessor| self.nodes_by_slot[predecessor])
71    }
72
73    #[must_use]
74    pub fn path_to(&self, target: Node) -> Option<SignedPath<Node>> {
75        let total_cost = self.distance_to(target)?;
76        let mut nodes = vec![target];
77        let mut cursor = target;
78        while cursor != self.source {
79            cursor = self.predecessor(cursor)?;
80            nodes.push(cursor);
81            if nodes.len() > self.nodes.len() {
82                return None;
83            }
84        }
85        nodes.reverse();
86        Some(SignedPath { nodes, total_cost })
87    }
88}
89
90/// Computes signed shortest paths from `source`.
91///
92/// # Errors
93///
94/// Returns an error for arithmetic overflow or a reachable negative cycle.
95pub fn bellman_ford<G, F>(
96    graph: &G,
97    source: G::Node,
98    edge_cost: F,
99) -> Result<Option<BellmanFord<G::Node>>>
100where
101    G: IndexGraphView,
102    F: Fn(G::Edge) -> i64,
103{
104    bellman_ford_filtered(graph, source, |edge| Some(edge_cost(edge)))
105}
106
107/// Computes signed shortest paths using only edges with a returned cost.
108///
109/// # Errors
110///
111/// Returns an error for arithmetic overflow or a reachable negative cycle.
112pub fn bellman_ford_filtered<G, F>(
113    graph: &G,
114    source: G::Node,
115    edge_cost: F,
116) -> Result<Option<BellmanFord<G::Node>>>
117where
118    G: IndexGraphView,
119    F: Fn(G::Edge) -> Option<i64>,
120{
121    if !graph.contains_node(source) {
122        return Ok(None);
123    }
124    let mut nodes_by_slot = vec![None; graph.node_bound()];
125    let nodes = graph.node_indices().collect::<Vec<_>>();
126    for &node in &nodes {
127        nodes_by_slot[G::node_slot(node)] = Some(node);
128    }
129    let mut edges = Vec::with_capacity(graph.edge_count());
130    for (edge, endpoints) in graph.edge_references() {
131        if let Some(weight) = edge_cost(edge) {
132            edges.push((
133                G::node_slot(endpoints.source()),
134                G::node_slot(endpoints.target()),
135                weight,
136            ));
137        }
138    }
139    let mut distances = vec![0_i64; graph.node_bound()];
140    let mut reachable = vec![false; graph.node_bound()];
141    let mut predecessors = vec![None; graph.node_bound()];
142    reachable[G::node_slot(source)] = true;
143    for _ in 1..nodes.len() {
144        if !relax_all(&edges, &mut distances, &mut reachable, &mut predecessors)? {
145            break;
146        }
147    }
148    reject_negative_cycle(&edges, &distances, &reachable)?;
149
150    Ok(Some(BellmanFord {
151        source,
152        nodes,
153        nodes_by_slot,
154        distances,
155        reachable,
156        predecessors,
157        node_slot: G::node_slot,
158    }))
159}
160
161fn relax_all(
162    edges: &[(usize, usize, i64)],
163    distances: &mut [i64],
164    reachable: &mut [bool],
165    predecessors: &mut [Option<usize>],
166) -> Result<bool> {
167    let mut changed = false;
168    for &(source, target, weight) in edges {
169        if !reachable[source] {
170            continue;
171        }
172        let candidate =
173            distances[source]
174                .checked_add(weight)
175                .ok_or(GraphError::ArithmeticOverflow {
176                    operation: "Bellman-Ford edge relaxation",
177                })?;
178        if !reachable[target] || candidate < distances[target] {
179            distances[target] = candidate;
180            reachable[target] = true;
181            predecessors[target] = Some(source);
182            changed = true;
183        }
184    }
185    Ok(changed)
186}
187
188fn reject_negative_cycle(
189    edges: &[(usize, usize, i64)],
190    distances: &[i64],
191    reachable: &[bool],
192) -> Result<()> {
193    for &(source, target, weight) in edges {
194        if !reachable[source] {
195            continue;
196        }
197        let candidate =
198            distances[source]
199                .checked_add(weight)
200                .ok_or(GraphError::ArithmeticOverflow {
201                    operation: "Bellman-Ford cycle check",
202                })?;
203        if reachable[target] && candidate < distances[target] {
204            return Err(GraphError::NegativeCycle {
205                algorithm: "Bellman-Ford",
206            });
207        }
208    }
209    Ok(())
210}