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