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::{FieldInfo, ProblemSchemaEntry, ProblemSizeFieldEntry};
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        module_path: module_path!(),
19        description: "Integral flow feasibility on a directed graph with overlapping bundle capacities",
20        fields: &[
21            FieldInfo { name: "graph", type_name: "DirectedGraph", description: "Directed graph G=(V,A)" },
22            FieldInfo { name: "source", type_name: "usize", description: "Source vertex s" },
23            FieldInfo { name: "sink", type_name: "usize", description: "Sink vertex t" },
24            FieldInfo { name: "bundles", type_name: "Vec<Vec<usize>>", description: "Bundles of arc indices covering A" },
25            FieldInfo { name: "bundle_capacities", type_name: "Vec<u64>", description: "Capacity c_j for each bundle I_j" },
26            FieldInfo { name: "requirement", type_name: "u64", description: "Required net inflow R at the sink" },
27        ],
28    }
29}
30
31inventory::submit! {
32    ProblemSizeFieldEntry {
33        name: "IntegralFlowBundles",
34        fields: &["num_vertices", "num_arcs", "num_bundles"],
35    }
36}
37
38/// Integral Flow with Bundles (Garey & Johnson ND36).
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct IntegralFlowBundles {
41    graph: DirectedGraph,
42    source: usize,
43    sink: usize,
44    bundles: Vec<Vec<usize>>,
45    bundle_capacities: Vec<u64>,
46    requirement: u64,
47}
48
49impl IntegralFlowBundles {
50    /// Create a new Integral Flow with Bundles instance.
51    pub fn new(
52        graph: DirectedGraph,
53        source: usize,
54        sink: usize,
55        bundles: Vec<Vec<usize>>,
56        bundle_capacities: Vec<u64>,
57        requirement: u64,
58    ) -> Self {
59        let num_vertices = graph.num_vertices();
60        let num_arcs = graph.num_arcs();
61
62        assert!(
63            source < num_vertices,
64            "source ({source}) >= num_vertices ({num_vertices})"
65        );
66        assert!(
67            sink < num_vertices,
68            "sink ({sink}) >= num_vertices ({num_vertices})"
69        );
70        assert!(source != sink, "source and sink must be distinct");
71        assert_eq!(
72            bundles.len(),
73            bundle_capacities.len(),
74            "bundles length must match bundle_capacities length"
75        );
76        assert!(requirement > 0, "requirement must be positive");
77
78        let mut arc_covered = vec![false; num_arcs];
79        let mut arc_upper_bounds = vec![u64::MAX; num_arcs];
80
81        for (bundle_index, (bundle, &capacity)) in
82            bundles.iter().zip(&bundle_capacities).enumerate()
83        {
84            assert!(
85                capacity > 0,
86                "bundle capacity at index {bundle_index} must be positive"
87            );
88
89            let mut seen = BTreeSet::new();
90            for &arc_index in bundle {
91                assert!(
92                    arc_index < num_arcs,
93                    "bundle {bundle_index} references arc {arc_index}, but num_arcs is {num_arcs}"
94                );
95                assert!(
96                    seen.insert(arc_index),
97                    "bundle {bundle_index} contains duplicate arc index {arc_index}"
98                );
99                arc_covered[arc_index] = true;
100                arc_upper_bounds[arc_index] = arc_upper_bounds[arc_index].min(capacity);
101            }
102        }
103
104        for (arc_index, covered) in arc_covered.iter().copied().enumerate() {
105            assert!(
106                covered,
107                "arc {arc_index} must belong to at least one bundle"
108            );
109            let domain = usize::try_from(arc_upper_bounds[arc_index])
110                .ok()
111                .and_then(|bound| bound.checked_add(1));
112            assert!(
113                domain.is_some(),
114                "bundle-derived upper bound for arc {arc_index} must fit into usize for dims()"
115            );
116        }
117
118        Self {
119            graph,
120            source,
121            sink,
122            bundles,
123            bundle_capacities,
124            requirement,
125        }
126    }
127
128    /// Get the underlying directed graph.
129    pub fn graph(&self) -> &DirectedGraph {
130        &self.graph
131    }
132
133    /// Get the source vertex.
134    pub fn source(&self) -> usize {
135        self.source
136    }
137
138    /// Get the sink vertex.
139    pub fn sink(&self) -> usize {
140        self.sink
141    }
142
143    /// Get the bundles.
144    pub fn bundles(&self) -> &[Vec<usize>] {
145        &self.bundles
146    }
147
148    /// Get the bundle capacities.
149    pub fn bundle_capacities(&self) -> &[u64] {
150        &self.bundle_capacities
151    }
152
153    /// Get the required net inflow at the sink.
154    pub fn requirement(&self) -> u64 {
155        self.requirement
156    }
157
158    /// Get the number of vertices.
159    pub fn num_vertices(&self) -> usize {
160        self.graph.num_vertices()
161    }
162
163    /// Get the number of arcs.
164    pub fn num_arcs(&self) -> usize {
165        self.graph.num_arcs()
166    }
167
168    /// Get the number of bundles.
169    pub fn num_bundles(&self) -> usize {
170        self.bundles.len()
171    }
172
173    /// Check whether a configuration is feasible.
174    pub fn is_valid_solution(&self, config: &[usize]) -> bool {
175        self.evaluate(config).0
176    }
177
178    fn arc_upper_bounds(&self) -> Vec<u64> {
179        let mut upper_bounds = vec![u64::MAX; self.num_arcs()];
180        for (bundle, &capacity) in self.bundles.iter().zip(&self.bundle_capacities) {
181            for &arc_index in bundle {
182                upper_bounds[arc_index] = upper_bounds[arc_index].min(capacity);
183            }
184        }
185        upper_bounds
186    }
187
188    fn vertex_balance(&self, config: &[usize], vertex: usize) -> Option<i128> {
189        let mut balance = 0i128;
190        for (arc_index, (u, v)) in self.graph.arcs().into_iter().enumerate() {
191            let flow = i128::from(u64::try_from(*config.get(arc_index)?).ok()?);
192            if vertex == u {
193                balance -= flow;
194            }
195            if vertex == v {
196                balance += flow;
197            }
198        }
199        Some(balance)
200    }
201}
202
203impl Problem for IntegralFlowBundles {
204    const NAME: &'static str = "IntegralFlowBundles";
205    type Value = crate::types::Or;
206
207    fn dims(&self) -> Vec<usize> {
208        self.arc_upper_bounds()
209            .into_iter()
210            .map(|bound| {
211                usize::try_from(bound)
212                    .ok()
213                    .and_then(|bound| bound.checked_add(1))
214                    .expect("bundle-derived arc upper bounds are validated in the constructor")
215            })
216            .collect()
217    }
218
219    fn evaluate(&self, config: &[usize]) -> crate::types::Or {
220        crate::types::Or({
221            if config.len() != self.num_arcs() {
222                return crate::types::Or(false);
223            }
224
225            let upper_bounds = self.arc_upper_bounds();
226            for (&value, &upper_bound) in config.iter().zip(&upper_bounds) {
227                if u64::try_from(value).map_or(true, |value| value > upper_bound) {
228                    return crate::types::Or(false);
229                }
230            }
231
232            for (bundle, &capacity) in self.bundles.iter().zip(&self.bundle_capacities) {
233                let mut total = 0u64;
234                for &arc_index in bundle {
235                    let Ok(flow) = u64::try_from(config[arc_index]) else {
236                        return crate::types::Or(false);
237                    };
238                    let Some(next_total) = total.checked_add(flow) else {
239                        return crate::types::Or(false);
240                    };
241                    total = next_total;
242                }
243                if total > capacity {
244                    return crate::types::Or(false);
245                }
246            }
247
248            for vertex in 0..self.num_vertices() {
249                if vertex == self.source || vertex == self.sink {
250                    continue;
251                }
252                if self.vertex_balance(config, vertex) != Some(0) {
253                    return crate::types::Or(false);
254                }
255            }
256
257            matches!(
258                self.vertex_balance(config, self.sink),
259                Some(balance) if balance >= i128::from(self.requirement)
260            )
261        })
262    }
263
264    fn variant() -> Vec<(&'static str, &'static str)> {
265        crate::variant_params![]
266    }
267}
268
269crate::declare_variants! {
270    default IntegralFlowBundles => "2^num_arcs",
271}
272
273#[cfg(feature = "example-db")]
274pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
275    vec![crate::example_db::specs::ModelExampleSpec {
276        id: "integral_flow_bundles",
277        instance: Box::new(IntegralFlowBundles::new(
278            DirectedGraph::new(4, vec![(0, 1), (0, 2), (1, 3), (2, 3), (1, 2), (2, 1)]),
279            0,
280            3,
281            vec![vec![0, 1], vec![2, 5], vec![3, 4]],
282            vec![1, 1, 1],
283            1,
284        )),
285        optimal_config: vec![1, 0, 1, 0, 0, 0],
286        optimal_value: serde_json::json!(true),
287    }]
288}
289
290#[cfg(test)]
291#[path = "../../unit_tests/models/graph/integral_flow_bundles.rs"]
292mod tests;