1use crate::error::{GraphalgError, GraphalgResult};
30use crate::max_flow::edmonds_karp::FlowNetwork;
31use crate::max_flow::min_cut::min_cut_from_max_flow;
32
33const EPS: f64 = 1.0e-9;
35
36#[derive(Debug, Clone, Copy, PartialEq)]
38pub struct ParametricArc {
39 pub from: usize,
41 pub to: usize,
43 pub base: f64,
45 pub slope: f64,
47}
48
49#[derive(Debug, Clone, PartialEq)]
51pub struct ParametricSolution {
52 pub lambda: f64,
54 pub flow_value: f64,
56 pub source_set: Vec<usize>,
59}
60
61#[derive(Debug, Clone, PartialEq)]
63pub struct ParametricBreakpoint {
64 pub lambda: f64,
66 pub source_set: Vec<usize>,
69}
70
71#[derive(Debug, Clone)]
73pub struct ParametricMaxFlow {
74 n: usize,
75 source: usize,
76 sink: usize,
77 arcs: Vec<ParametricArc>,
78}
79
80impl ParametricMaxFlow {
81 pub fn new(n: usize, source: usize, sink: usize) -> GraphalgResult<Self> {
83 if n == 0 {
84 return Err(GraphalgError::EmptyInput);
85 }
86 if source >= n || sink >= n {
87 return Err(GraphalgError::SourceOutOfRange {
88 node: source.max(sink),
89 n,
90 });
91 }
92 if source == sink {
93 return Err(GraphalgError::InvalidParameter(
94 "source and sink must differ".to_string(),
95 ));
96 }
97 Ok(Self {
98 n,
99 source,
100 sink,
101 arcs: Vec::new(),
102 })
103 }
104
105 pub fn num_nodes(&self) -> usize {
107 self.n
108 }
109
110 pub fn source(&self) -> usize {
112 self.source
113 }
114
115 pub fn sink(&self) -> usize {
117 self.sink
118 }
119
120 pub fn arcs(&self) -> &[ParametricArc] {
122 &self.arcs
123 }
124
125 pub fn add_arc(&mut self, from: usize, to: usize, base: f64, slope: f64) -> GraphalgResult<()> {
130 if from >= self.n || to >= self.n {
131 return Err(GraphalgError::IndexOutOfBounds {
132 index: from.max(to),
133 len: self.n,
134 });
135 }
136 if from == to {
137 return Err(GraphalgError::InvalidParameter(format!(
138 "self-loop arc at node {from} is not allowed"
139 )));
140 }
141 if !base.is_finite() || !slope.is_finite() {
142 return Err(GraphalgError::InvalidEdgeWeight(format!(
143 "arc ({from},{to}) has non-finite coefficients base={base} slope={slope}"
144 )));
145 }
146 let is_source_arc = from == self.source;
147 let is_sink_arc = to == self.sink;
148 if is_source_arc && slope < -EPS {
149 return Err(GraphalgError::InvalidConfiguration(format!(
150 "source arc ({from},{to}) must be non-decreasing in lambda (slope {slope} < 0)"
151 )));
152 }
153 if is_sink_arc && slope > EPS {
154 return Err(GraphalgError::InvalidConfiguration(format!(
155 "sink arc ({from},{to}) must be non-increasing in lambda (slope {slope} > 0)"
156 )));
157 }
158 if !is_source_arc && !is_sink_arc && slope.abs() > EPS {
159 return Err(GraphalgError::InvalidConfiguration(format!(
160 "interior arc ({from},{to}) must have constant capacity (slope {slope} != 0)"
161 )));
162 }
163 self.arcs.push(ParametricArc {
164 from,
165 to,
166 base,
167 slope,
168 });
169 Ok(())
170 }
171
172 pub fn from_linear_capacities(
178 n: usize,
179 source: usize,
180 sink: usize,
181 from: &[usize],
182 to: &[usize],
183 base: &[f64],
184 slope: &[f64],
185 ) -> GraphalgResult<Self> {
186 let m = from.len();
187 if to.len() != m {
188 return Err(GraphalgError::DimensionMismatch { a: m, b: to.len() });
189 }
190 if base.len() != m {
191 return Err(GraphalgError::DimensionMismatch {
192 a: m,
193 b: base.len(),
194 });
195 }
196 if slope.len() != m {
197 return Err(GraphalgError::DimensionMismatch {
198 a: m,
199 b: slope.len(),
200 });
201 }
202 let mut net = Self::new(n, source, sink)?;
203 for i in 0..m {
204 net.add_arc(from[i], to[i], base[i], slope[i])?;
205 }
206 Ok(net)
207 }
208
209 fn arc_capacity(arc: &ParametricArc, lambda: f64) -> f64 {
211 (arc.base + arc.slope * lambda).max(0.0)
212 }
213
214 fn build_network(&self, lambda: f64) -> GraphalgResult<FlowNetwork> {
216 if !lambda.is_finite() {
217 return Err(GraphalgError::InvalidParameter(format!(
218 "lambda must be finite, got {lambda}"
219 )));
220 }
221 let mut net = FlowNetwork::new(self.n);
222 for arc in &self.arcs {
223 let cap = Self::arc_capacity(arc, lambda);
224 if cap > 0.0 {
225 net.add_edge(arc.from, arc.to, cap)?;
226 }
227 }
228 Ok(net)
229 }
230
231 pub fn solve_at(&self, lambda: f64) -> GraphalgResult<ParametricSolution> {
234 let net = self.build_network(lambda)?;
235 let cut = min_cut_from_max_flow(&net, self.source, self.sink)?;
236 let mut source_set = cut.source_side;
237 source_set.sort_unstable();
238 Ok(ParametricSolution {
239 lambda,
240 flow_value: cut.value,
241 source_set,
242 })
243 }
244
245 pub fn solve_grid(&self, lambdas: &[f64]) -> GraphalgResult<Vec<ParametricSolution>> {
250 let mut out = Vec::with_capacity(lambdas.len());
251 for &lambda in lambdas {
252 out.push(self.solve_at(lambda)?);
253 }
254 Ok(out)
255 }
256
257 fn cut_line(&self, in_source: &[bool]) -> (f64, f64) {
262 let mut intercept = 0.0;
263 let mut slope = 0.0;
264 for arc in &self.arcs {
265 if in_source[arc.from] && !in_source[arc.to] {
266 intercept += arc.base;
267 slope += arc.slope;
268 }
269 }
270 (intercept, slope)
271 }
272
273 fn membership(&self, source_set: &[usize]) -> Vec<bool> {
275 let mut mask = vec![false; self.n];
276 for &v in source_set {
277 mask[v] = true;
278 }
279 mask
280 }
281
282 pub fn find_breakpoints(
290 &self,
291 lambda_min: f64,
292 lambda_max: f64,
293 ) -> GraphalgResult<Vec<ParametricBreakpoint>> {
294 if !lambda_min.is_finite() || !lambda_max.is_finite() {
295 return Err(GraphalgError::InvalidParameter(
296 "lambda bounds must be finite".to_string(),
297 ));
298 }
299 if lambda_min > lambda_max {
300 return Err(GraphalgError::InvalidParameter(format!(
301 "lambda_min ({lambda_min}) must not exceed lambda_max ({lambda_max})"
302 )));
303 }
304 let lo = self.slice_end(lambda_min)?;
305 let hi = self.slice_end(lambda_max)?;
306 let mut out = Vec::new();
307 let budget = 8 * self.n + 16;
308 self.slice(&lo, &hi, budget, &mut out)?;
309 out.sort_by(|a, b| {
310 a.lambda
311 .partial_cmp(&b.lambda)
312 .unwrap_or(std::cmp::Ordering::Equal)
313 });
314 Ok(out)
315 }
316
317 fn slice_end(&self, lambda: f64) -> GraphalgResult<SliceEnd> {
319 let sol = self.solve_at(lambda)?;
320 let mask = self.membership(&sol.source_set);
321 let line = self.cut_line(&mask);
322 Ok(SliceEnd {
323 lambda,
324 flow_value: sol.flow_value,
325 source_set: sol.source_set,
326 line,
327 })
328 }
329
330 fn slice(
331 &self,
332 lo: &SliceEnd,
333 hi: &SliceEnd,
334 budget: usize,
335 out: &mut Vec<ParametricBreakpoint>,
336 ) -> GraphalgResult<()> {
337 if budget == 0 {
338 return Err(GraphalgError::NotConverged {
339 iter: 8 * self.n + 16,
340 });
341 }
342 if lo.source_set == hi.source_set {
343 return Ok(());
344 }
345 let (a_lo, b_lo) = lo.line;
346 let (a_hi, b_hi) = hi.line;
347 let denom = b_lo - b_hi;
348 if denom.abs() <= EPS {
349 out.push(ParametricBreakpoint {
352 lambda: hi.lambda,
353 source_set: hi.source_set.clone(),
354 });
355 return Ok(());
356 }
357 let lam_star = (a_hi - a_lo) / denom;
358 if lam_star <= lo.lambda + EPS || lam_star >= hi.lambda - EPS {
359 let clamped = lam_star.clamp(lo.lambda, hi.lambda);
363 out.push(ParametricBreakpoint {
364 lambda: clamped,
365 source_set: hi.source_set.clone(),
366 });
367 return Ok(());
368 }
369 let mid = self.slice_end(lam_star)?;
370 let y_int = a_lo + b_lo * lam_star;
371 if mid.flow_value >= y_int - EPS {
372 out.push(ParametricBreakpoint {
375 lambda: lam_star,
376 source_set: hi.source_set.clone(),
377 });
378 Ok(())
379 } else {
380 self.slice(lo, &mid, budget - 1, out)?;
381 self.slice(&mid, hi, budget - 1, out)?;
382 Ok(())
383 }
384 }
385}
386
387#[derive(Debug, Clone)]
389struct SliceEnd {
390 lambda: f64,
391 flow_value: f64,
392 source_set: Vec<usize>,
393 line: (f64, f64),
394}
395
396#[cfg(test)]
397mod tests {
398 use super::*;
399
400 fn approx(a: f64, b: f64) -> bool {
401 (a - b).abs() < 1e-7
402 }
403
404 fn independent_flow(net: &ParametricMaxFlow, lambda: f64) -> f64 {
406 let fnet = net.build_network(lambda).expect("build");
407 crate::max_flow::dinic::dinic_max_flow(&fnet, net.source(), net.sink()).expect("dinic")
408 }
409
410 fn nested_net() -> ParametricMaxFlow {
413 let mut net = ParametricMaxFlow::new(4, 0, 3).expect("new");
414 net.add_arc(0, 1, 0.0, 1.0).expect("s->a");
415 net.add_arc(0, 2, 0.0, 1.0).expect("s->b");
416 net.add_arc(1, 3, 1.0, 0.0).expect("a->t");
417 net.add_arc(2, 3, 1.0, 0.0).expect("b->t");
418 net
419 }
420
421 #[test]
422 fn grid_matches_independent_solves() {
423 let net = nested_net();
424 for k in 0..=20 {
425 let lambda = k as f64 * 0.2;
426 let sol = net.solve_at(lambda).expect("solve");
427 let indep = independent_flow(&net, lambda);
428 assert!(
429 approx(sol.flow_value, indep),
430 "lambda={lambda} parametric={} independent={indep}",
431 sol.flow_value
432 );
433 }
434 }
435
436 #[test]
437 fn source_sets_are_nested() {
438 let net = nested_net();
439 let lambdas: Vec<f64> = (0..=20).map(|k| k as f64 * 0.25).collect();
440 let sols = net.solve_grid(&lambdas).expect("grid");
441 for w in sols.windows(2) {
442 let prev: std::collections::BTreeSet<usize> = w[0].source_set.iter().copied().collect();
443 let next: std::collections::BTreeSet<usize> = w[1].source_set.iter().copied().collect();
444 assert!(
445 prev.is_subset(&next),
446 "not nested at lambda {} -> {}: {:?} vs {:?}",
447 w[0].lambda,
448 w[1].lambda,
449 w[0].source_set,
450 w[1].source_set
451 );
452 }
453 assert_eq!(sols.first().expect("first").source_set, vec![0]);
455 assert!(sols.last().expect("last").source_set.len() >= 3);
456 }
457
458 #[test]
459 fn constant_capacities_give_constant_flow() {
460 let mut net = ParametricMaxFlow::new(4, 0, 3).expect("new");
461 net.add_arc(0, 1, 3.0, 0.0).expect("e");
462 net.add_arc(0, 2, 2.0, 0.0).expect("e");
463 net.add_arc(1, 3, 3.0, 0.0).expect("e");
464 net.add_arc(2, 3, 2.0, 0.0).expect("e");
465 let lambdas: Vec<f64> = (-5..=5).map(|k| k as f64).collect();
466 let sols = net.solve_grid(&lambdas).expect("grid");
467 for s in &sols {
468 assert!(
469 approx(s.flow_value, 5.0),
470 "flow {} != 5 at {}",
471 s.flow_value,
472 s.lambda
473 );
474 }
475 let first = &sols[0].source_set;
477 for s in &sols {
478 assert_eq!(&s.source_set, first);
479 }
480 }
481
482 #[test]
483 fn analytic_two_node_min_formula() {
484 let other = 2.5;
487 let mut net = ParametricMaxFlow::new(3, 0, 2).expect("new");
488 net.add_arc(0, 1, 0.0, 1.0).expect("s->a");
489 net.add_arc(1, 2, other, 0.0).expect("a->t");
490 for k in 0..=12 {
491 let lambda = k as f64 * 0.5;
492 let sol = net.solve_at(lambda).expect("solve");
493 let expected = lambda.max(0.0).min(other);
494 assert!(
495 approx(sol.flow_value, expected),
496 "lambda={lambda}: got {} expected {expected}",
497 sol.flow_value
498 );
499 }
500 }
501
502 #[test]
503 fn breakpoint_detected_for_nested_example() {
504 let net = nested_net();
505 let bps = net.find_breakpoints(0.0, 3.0).expect("breakpoints");
508 assert!(!bps.is_empty(), "expected at least one breakpoint");
509 let near_one = bps.iter().any(|b| (b.lambda - 1.0).abs() < 1e-4);
510 assert!(near_one, "expected a breakpoint near lambda=1, got {bps:?}");
511 }
512
513 #[test]
514 fn rejects_non_monotone_arcs() {
515 let mut net = ParametricMaxFlow::new(4, 0, 3).expect("new");
516 assert!(net.add_arc(0, 1, 1.0, -1.0).is_err());
518 assert!(net.add_arc(1, 3, 1.0, 1.0).is_err());
520 assert!(net.add_arc(1, 2, 1.0, 0.5).is_err());
522 }
523
524 #[test]
525 fn rejects_bad_construction() {
526 assert!(ParametricMaxFlow::new(0, 0, 0).is_err());
527 assert!(ParametricMaxFlow::new(3, 0, 0).is_err());
528 assert!(ParametricMaxFlow::new(3, 5, 1).is_err());
529 let from = [0usize, 1];
531 let to = [1usize, 2];
532 let base = [1.0];
533 let slope = [0.0, 0.0];
534 assert!(
535 ParametricMaxFlow::from_linear_capacities(3, 0, 2, &from, &to, &base, &slope).is_err()
536 );
537 }
538
539 #[test]
540 fn from_linear_capacities_builds() {
541 let from = [0usize, 0, 1, 2];
542 let to = [1usize, 2, 3, 3];
543 let base = [0.0, 0.0, 1.0, 1.0];
544 let slope = [1.0, 1.0, 0.0, 0.0];
545 let net = ParametricMaxFlow::from_linear_capacities(4, 0, 3, &from, &to, &base, &slope)
546 .expect("build");
547 assert_eq!(net.arcs().len(), 4);
548 let sol = net.solve_at(0.5).expect("solve");
549 assert!(approx(sol.flow_value, 1.0));
550 }
551}