Skip to main content

problemreductions/models/graph/
integral_flow_bundles.rs

1//! Integral Flow with Bundles problem implementation.
2//!
3//! Given a directed graph with overlapping bundle-capacity constraints on arcs,
4//! determine whether an integral flow can deliver a required amount to the sink.
5
6use crate::registry::{CreateSpec, ProblemSchemaEntry};
7use crate::topology::DirectedGraph;
8use crate::traits::Problem;
9use serde::{Deserialize, Serialize};
10use std::collections::BTreeSet;
11
12inventory::submit! {
13    ProblemSchemaEntry {
14        name: "IntegralFlowBundles",
15        display_name: "Integral Flow with Bundles",
16        aliases: &[],
17        dimensions: &[],
18        category: crate::registry::ProblemCategory::Graph,
19        module_path: module_path!(),
20        description: "Integral flow feasibility on a directed graph with overlapping bundle capacities",
21        fields: IntegralFlowBundlesCreateSpec::FIELDS,
22    }
23}
24
25/// Integral Flow with Bundles (Garey & Johnson ND36).
26#[derive(Debug, Clone, Serialize, Deserialize)]
27#[serde(try_from = "IntegralFlowBundlesData")]
28pub struct IntegralFlowBundles {
29    graph: DirectedGraph,
30    source: usize,
31    sink: usize,
32    bundles: Vec<Vec<usize>>,
33    bundle_capacities: Vec<i64>,
34    requirement: i64,
35}
36
37#[derive(Deserialize)]
38struct IntegralFlowBundlesData {
39    graph: DirectedGraph,
40    source: usize,
41    sink: usize,
42    bundles: Vec<Vec<usize>>,
43    bundle_capacities: Vec<i64>,
44    requirement: i64,
45}
46
47impl TryFrom<IntegralFlowBundlesData> for IntegralFlowBundles {
48    type Error = crate::registry::ConstructionError;
49    fn try_from(data: IntegralFlowBundlesData) -> Result<Self, Self::Error> {
50        Self::try_new(
51            data.graph,
52            data.source,
53            data.sink,
54            data.bundles,
55            data.bundle_capacities,
56            data.requirement,
57        )
58    }
59}
60
61#[derive(Debug, Deserialize, crate::CreateSpec)]
62struct IntegralFlowBundlesCreateSpec {
63    #[create(codec = "arc-list")]
64    arcs: Vec<(usize, usize)>,
65    num_vertices: Option<usize>,
66    #[create(codec = "semicolon-separated")]
67    bundles: Vec<Vec<usize>>,
68    #[create(codec = "comma-separated")]
69    bundle_capacities: Vec<i64>,
70    source: usize,
71    sink: usize,
72    requirement: i64,
73}
74
75impl TryFrom<IntegralFlowBundlesCreateSpec> for IntegralFlowBundles {
76    type Error = crate::registry::ConstructionError;
77    fn try_from(
78        spec: IntegralFlowBundlesCreateSpec,
79    ) -> Result<Self, crate::registry::ConstructionError> {
80        if spec.arcs.is_empty() {
81            return Err("arcs must be non-empty".into());
82        }
83        let inferred = spec
84            .arcs
85            .iter()
86            .flat_map(|&(u, v)| [u, v])
87            .max()
88            .map(|v| v.checked_add(1).ok_or("vertex count overflows usize"))
89            .transpose()?
90            .unwrap_or(0);
91        let count = spec.num_vertices.unwrap_or(inferred);
92        if count < inferred {
93            return Err("num_vertices is too small".into());
94        }
95        Self::try_new(
96            DirectedGraph::new(count, spec.arcs),
97            spec.source,
98            spec.sink,
99            spec.bundles,
100            spec.bundle_capacities,
101            spec.requirement,
102        )
103    }
104}
105
106impl IntegralFlowBundles {
107    /// Create a new Integral Flow with Bundles instance.
108    pub fn new(
109        graph: DirectedGraph,
110        source: usize,
111        sink: usize,
112        bundles: Vec<Vec<usize>>,
113        bundle_capacities: Vec<i64>,
114        requirement: i64,
115    ) -> Self {
116        Self::try_new(graph, source, sink, bundles, bundle_capacities, requirement)
117            .unwrap_or_else(|error| panic!("{error}"))
118    }
119
120    fn try_new(
121        graph: DirectedGraph,
122        source: usize,
123        sink: usize,
124        bundles: Vec<Vec<usize>>,
125        bundle_capacities: Vec<i64>,
126        requirement: i64,
127    ) -> Result<Self, crate::registry::ConstructionError> {
128        let num_vertices = graph.num_vertices();
129        let num_arcs = graph.num_arcs();
130
131        if source >= num_vertices {
132            return Err(format!("source ({source}) >= num_vertices ({num_vertices})").into());
133        }
134        if sink >= num_vertices {
135            return Err(format!("sink ({sink}) >= num_vertices ({num_vertices})").into());
136        }
137        if source == sink {
138            return Err("source and sink must be distinct".into());
139        }
140        if bundles.len() != bundle_capacities.len() {
141            return Err(crate::registry::ConstructionError::length_mismatch(
142                "bundles",
143                bundles.len(),
144                bundle_capacities.len(),
145            ));
146        }
147        if requirement <= 0 {
148            return Err("requirement must be positive".into());
149        }
150
151        let mut arc_covered = vec![false; num_arcs];
152        let mut arc_upper_bounds = vec![i64::MAX; num_arcs];
153
154        for (bundle_index, (bundle, &capacity)) in
155            bundles.iter().zip(&bundle_capacities).enumerate()
156        {
157            if capacity <= 0 {
158                return Err(
159                    format!("bundle capacity at index {bundle_index} must be positive").into(),
160                );
161            }
162
163            let mut seen = BTreeSet::new();
164            for &arc_index in bundle {
165                if arc_index >= num_arcs {
166                    return Err(format!("bundle {bundle_index} arc is out of range: index {arc_index}, num_arcs {num_arcs}").into());
167                }
168                if !(seen.insert(arc_index)) {
169                    return Err(format!(
170                        "bundle {bundle_index} contains duplicate arc index {arc_index}"
171                    )
172                    .into());
173                }
174                arc_covered[arc_index] = true;
175                arc_upper_bounds[arc_index] = arc_upper_bounds[arc_index].min(capacity);
176            }
177        }
178
179        for (arc_index, covered) in arc_covered.iter().copied().enumerate() {
180            if !(covered) {
181                return Err(format!("arc {arc_index} must belong to at least one bundle").into());
182            }
183            if usize::try_from(arc_upper_bounds[arc_index])
184                .ok()
185                .and_then(|bound| bound.checked_add(1))
186                .is_none()
187            {
188                return Err(format!("bundle-derived upper bound for arc {arc_index} must fit into usize for dimensions()").into());
189            }
190        }
191
192        Ok(Self {
193            graph,
194            source,
195            sink,
196            bundles,
197            bundle_capacities,
198            requirement,
199        })
200    }
201
202    /// Get the underlying directed graph.
203    pub fn graph(&self) -> &DirectedGraph {
204        &self.graph
205    }
206
207    /// Get the source vertex.
208    pub fn source(&self) -> usize {
209        self.source
210    }
211
212    /// Get the sink vertex.
213    pub fn sink(&self) -> usize {
214        self.sink
215    }
216
217    /// Get the bundles.
218    pub fn bundles(&self) -> &[Vec<usize>] {
219        &self.bundles
220    }
221
222    /// Get the bundle capacities.
223    pub fn bundle_capacities(&self) -> &[i64] {
224        &self.bundle_capacities
225    }
226
227    /// Maximum bit length of the bundle capacities, with a minimum of one.
228    pub fn max_capacity_bits(&self) -> u64 {
229        crate::types::max_numeric_magnitude_bits(self.bundle_capacities.iter().copied())
230    }
231
232    /// Get the required net inflow at the sink.
233    pub fn requirement(&self) -> i64 {
234        self.requirement
235    }
236
237    /// Get the number of vertices.
238    pub fn num_vertices(&self) -> usize {
239        self.graph.num_vertices()
240    }
241
242    /// Get the number of arcs.
243    pub fn num_arcs(&self) -> usize {
244        self.graph.num_arcs()
245    }
246
247    /// Get the number of bundles.
248    pub fn num_bundles(&self) -> usize {
249        self.bundles.len()
250    }
251
252    /// Check whether a configuration is feasible.
253    pub fn is_valid_solution(
254        &self,
255        config: &[usize],
256    ) -> Result<bool, crate::traits::EvaluationError> {
257        Ok(self.evaluate_solution(config)?.0)
258    }
259
260    pub(crate) fn arc_upper_bounds(&self) -> Vec<i64> {
261        let mut upper_bounds = vec![i64::MAX; self.num_arcs()];
262        for (bundle, &capacity) in self.bundles.iter().zip(&self.bundle_capacities) {
263            for &arc_index in bundle {
264                upper_bounds[arc_index] = upper_bounds[arc_index].min(capacity);
265            }
266        }
267        upper_bounds
268    }
269
270    fn vertex_balance(
271        &self,
272        config: &[usize],
273        vertex: usize,
274    ) -> Result<Option<i64>, crate::traits::EvaluationError> {
275        let mut balance = 0_i64;
276        for (arc_index, (u, v)) in self.graph.arcs().into_iter().enumerate() {
277            let Some(&raw_flow) = config.get(arc_index) else {
278                return Ok(None);
279            };
280            let flow = i64::try_from(raw_flow).map_err(|_| {
281                crate::traits::EvaluationError::IntegerOverflow(
282                    "converting bundled arc flow to i64".into(),
283                )
284            })?;
285            if vertex == u {
286                balance = balance.checked_sub(flow).ok_or_else(|| {
287                    crate::traits::EvaluationError::IntegerOverflow(
288                        "subtracting outgoing bundled flow".into(),
289                    )
290                })?;
291            }
292            if vertex == v {
293                balance = balance.checked_add(flow).ok_or_else(|| {
294                    crate::traits::EvaluationError::IntegerOverflow(
295                        "adding incoming bundled flow".into(),
296                    )
297                })?;
298            }
299        }
300        Ok(Some(balance))
301    }
302
303    fn evaluate_solution(
304        &self,
305        config: &[usize],
306    ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
307        if config.len() != self.num_arcs() {
308            return Err(crate::traits::EvaluationError::InvalidConfiguration(
309                "flow vector length does not match the graph arcs".into(),
310            ));
311        }
312
313        let upper_bounds = self.arc_upper_bounds();
314        for (&value, &upper_bound) in config.iter().zip(&upper_bounds) {
315            if i64::try_from(value).map_or(true, |value| value > upper_bound) {
316                return Ok(crate::types::Or(false));
317            }
318        }
319
320        for (bundle, &capacity) in self.bundles.iter().zip(&self.bundle_capacities) {
321            let mut total = 0i64;
322            for &arc_index in bundle {
323                let Ok(flow) = i64::try_from(config[arc_index]) else {
324                    return Ok(crate::types::Or(false));
325                };
326                let Some(next_total) = total.checked_add(flow) else {
327                    return Ok(crate::types::Or(false));
328                };
329                total = next_total;
330            }
331            if total > capacity {
332                return Ok(crate::types::Or(false));
333            }
334        }
335
336        for vertex in 0..self.num_vertices() {
337            if vertex == self.source || vertex == self.sink {
338                continue;
339            }
340            if self.vertex_balance(config, vertex)? != Some(0) {
341                return Ok(crate::types::Or(false));
342            }
343        }
344
345        Ok(crate::types::Or(matches!(
346            self.vertex_balance(config, self.sink)?,
347            Some(balance) if balance >= self.requirement
348        )))
349    }
350}
351
352impl Problem for IntegralFlowBundles {
353    const NAME: &'static str = "IntegralFlowBundles";
354    type Solution = Vec<usize>;
355    type Value = crate::types::Or;
356
357    crate::problem_parameters![
358        ("max_capacity_bits", max_capacity_bits),
359        ("num_arcs", num_arcs),
360        ("num_bundles", num_bundles),
361        ("num_vertices", num_vertices),
362    ];
363
364    fn evaluate(
365        &self,
366        config: &Self::Solution,
367    ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
368        self.evaluate_solution(config)
369    }
370
371    fn variant() -> Vec<(&'static str, &'static str)> {
372        crate::variant_params![]
373    }
374}
375
376impl crate::solvers::BruteForceProblem for IntegralFlowBundles {
377    fn dimensions(&self) -> Vec<usize> {
378        self.arc_upper_bounds()
379            .into_iter()
380            .map(|bound| {
381                usize::try_from(bound)
382                    .ok()
383                    .and_then(|bound| bound.checked_add(1))
384                    .expect("bundle-derived arc upper bounds are validated in the constructor")
385            })
386            .collect()
387    }
388}
389
390crate::declare_variants! {
391    default IntegralFlowBundles => "2^num_arcs" create IntegralFlowBundlesCreateSpec,
392}
393
394crate::register_brute_force! {
395    IntegralFlowBundles,
396}
397
398#[cfg(feature = "example-db")]
399pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
400    vec![crate::example_db::specs::ModelExampleSpec {
401        id: "integral_flow_bundles",
402        instance: Box::new(IntegralFlowBundles::new(
403            DirectedGraph::new(4, vec![(0, 1), (0, 2), (1, 3), (2, 3), (1, 2), (2, 1)]),
404            0,
405            3,
406            vec![vec![0, 1], vec![2, 5], vec![3, 4]],
407            vec![1, 1, 1],
408            1,
409        )),
410        optimal_config: serde_json::json!(vec![1, 0, 1, 0, 0, 0]),
411        optimal_value: serde_json::json!(true),
412    }]
413}
414
415#[cfg(test)]
416#[path = "../../unit_tests/models/graph/integral_flow_bundles.rs"]
417mod tests;