use std::collections::{HashMap, VecDeque};
use crate::graph::Graph;
use crate::types::{ulid_encode, DbError, Value};
use super::{opt_str, require_node_idx, GraphSnapshot, Row};
pub fn run_max_flow(graph: &Graph, params: &HashMap<String, Value>) -> Result<Vec<Row>, DbError> {
let cap_prop = opt_str(params, "capacity", "capacity")?;
let snap = GraphSnapshot::build(graph, None);
if snap.n == 0 {
return Ok(vec![]);
}
let source = require_node_idx(params, "source", &snap)?;
let sink = require_node_idx(params, "sink", &snap)?;
if source == sink {
return Err(DbError::Query(
"maxFlow: 'source' and 'sink' must be different nodes".into(),
));
}
let n = snap.n;
let mut cap: HashMap<(usize, usize), f64> = HashMap::new();
for edge in graph.all_edges() {
let (Some(&fi), Some(&ti)) = (
snap.id_to_idx.get(&edge.from_node),
snap.id_to_idx.get(&edge.to_node),
) else {
continue;
};
if fi == ti {
continue; }
let w: f64 = edge.properties.get(cap_prop)
.and_then(|v| match v {
Value::Float(f) => Some(*f),
Value::Int(i) => Some(*i as f64),
_ => None,
})
.unwrap_or(0.0);
if w < 0.0 {
return Err(DbError::Query(format!(
"maxFlow: negative capacity {w}. Capacities must be non-negative."
)));
}
*cap.entry((fi, ti)).or_insert(0.0) += w;
cap.entry((ti, fi)).or_insert(0.0); if !edge.directed {
*cap.entry((ti, fi)).or_insert(0.0) += w;
cap.entry((fi, ti)).or_insert(0.0);
}
}
let mut max_flow = 0.0f64;
loop {
let mut parent = vec![usize::MAX; n];
parent[source] = source;
let mut queue = VecDeque::new();
queue.push_back(source);
'bfs: while let Some(u) = queue.pop_front() {
for v in 0..n {
if parent[v] == usize::MAX && *cap.get(&(u, v)).unwrap_or(&0.0) > 1e-12 {
parent[v] = u;
if v == sink {
break 'bfs;
}
queue.push_back(v);
}
}
}
if parent[sink] == usize::MAX {
break; }
let mut bottleneck = f64::INFINITY;
let mut v = sink;
while v != source {
let u = parent[v];
let c = *cap.get(&(u, v)).unwrap_or(&0.0);
if c < bottleneck {
bottleneck = c;
}
v = u;
}
let mut v = sink;
while v != source {
let u = parent[v];
*cap.entry((u, v)).or_insert(0.0) -= bottleneck;
*cap.entry((v, u)).or_insert(0.0) += bottleneck;
v = u;
}
max_flow += bottleneck;
}
let mut row = HashMap::new();
row.insert(
"source".to_string(),
Value::String(ulid_encode(snap.node_ids[source].0)),
);
row.insert(
"sink".to_string(),
Value::String(ulid_encode(snap.node_ids[sink].0)),
);
row.insert("maxFlow".to_string(), Value::Float(max_flow));
Ok(vec![row])
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::Graph;
use crate::types::{ulid_encode, Edge, Node, NodeId, Value};
fn make_node(g: &mut Graph) -> NodeId {
let id = g.alloc_node_id();
g.apply_insert_node(Node {
id,
labels: vec!["N".into()],
properties: Default::default(),
});
id
}
fn make_cap_edge(g: &mut Graph, from: NodeId, to: NodeId, cap: f64) {
let id = g.alloc_edge_id();
g.apply_insert_edge(Edge {
id,
from_node: from,
to_node: to,
label: "E".into(),
properties: [("capacity".to_string(), Value::Float(cap))].into_iter().collect(),
directed: true,
});
}
#[test]
fn simple_two_edge_path() {
let mut g = Graph::new();
let s = make_node(&mut g);
let m = make_node(&mut g);
let t = make_node(&mut g);
make_cap_edge(&mut g, s, m, 10.0);
make_cap_edge(&mut g, m, t, 5.0);
let params: HashMap<String, Value> = [
("source".to_string(), Value::String(ulid_encode(s.0))),
("sink".to_string(), Value::String(ulid_encode(t.0))),
]
.into_iter()
.collect();
let rows = run_max_flow(&g, ¶ms).unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["maxFlow"], Value::Float(5.0));
}
#[test]
fn two_parallel_paths() {
let mut g = Graph::new();
let s = make_node(&mut g);
let a = make_node(&mut g);
let b = make_node(&mut g);
let t = make_node(&mut g);
make_cap_edge(&mut g, s, a, 3.0);
make_cap_edge(&mut g, a, t, 3.0);
make_cap_edge(&mut g, s, b, 4.0);
make_cap_edge(&mut g, b, t, 4.0);
let params: HashMap<String, Value> = [
("source".to_string(), Value::String(ulid_encode(s.0))),
("sink".to_string(), Value::String(ulid_encode(t.0))),
]
.into_iter()
.collect();
let rows = run_max_flow(&g, ¶ms).unwrap();
assert_eq!(rows[0]["maxFlow"], Value::Float(7.0));
}
#[test]
fn no_path_returns_zero_flow() {
let mut g = Graph::new();
let s = make_node(&mut g);
let t = make_node(&mut g);
let params: HashMap<String, Value> = [
("source".to_string(), Value::String(ulid_encode(s.0))),
("sink".to_string(), Value::String(ulid_encode(t.0))),
]
.into_iter()
.collect();
let rows = run_max_flow(&g, ¶ms).unwrap();
assert_eq!(rows[0]["maxFlow"], Value::Float(0.0));
}
#[test]
fn source_equals_sink_errors() {
let mut g = Graph::new();
let s = make_node(&mut g);
let params: HashMap<String, Value> = [
("source".to_string(), Value::String(ulid_encode(s.0))),
("sink".to_string(), Value::String(ulid_encode(s.0))),
]
.into_iter()
.collect();
assert!(run_max_flow(&g, ¶ms).is_err());
}
#[test]
fn classic_ford_fulkerson_graph() {
let mut g = Graph::new();
let s = make_node(&mut g);
let u = make_node(&mut g);
let v = make_node(&mut g);
let t = make_node(&mut g);
make_cap_edge(&mut g, s, u, 16.0);
make_cap_edge(&mut g, s, v, 13.0);
make_cap_edge(&mut g, u, t, 12.0);
make_cap_edge(&mut g, v, t, 20.0);
make_cap_edge(&mut g, u, v, 4.0);
make_cap_edge(&mut g, v, u, 9.0);
let params: HashMap<String, Value> = [
("source".to_string(), Value::String(ulid_encode(s.0))),
("sink".to_string(), Value::String(ulid_encode(t.0))),
]
.into_iter()
.collect();
let rows = run_max_flow(&g, ¶ms).unwrap();
assert_eq!(rows[0]["maxFlow"], Value::Float(29.0));
}
#[test]
fn missing_capacity_prop_treated_as_zero() {
let mut g = Graph::new();
let s = make_node(&mut g);
let t = make_node(&mut g);
let id = g.alloc_edge_id();
g.apply_insert_edge(crate::types::Edge {
id,
from_node: s,
to_node: t,
label: "E".into(),
properties: Default::default(), directed: true,
});
let params: HashMap<String, Value> = [
("source".to_string(), Value::String(ulid_encode(s.0))),
("sink".to_string(), Value::String(ulid_encode(t.0))),
]
.into_iter()
.collect();
let rows = run_max_flow(&g, ¶ms).unwrap();
assert_eq!(rows[0]["maxFlow"], Value::Float(0.0));
}
}