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)]
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 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 pub fn graph(&self) -> &DirectedGraph {
204 &self.graph
205 }
206
207 pub fn source(&self) -> usize {
209 self.source
210 }
211
212 pub fn sink(&self) -> usize {
214 self.sink
215 }
216
217 pub fn bundles(&self) -> &[Vec<usize>] {
219 &self.bundles
220 }
221
222 pub fn bundle_capacities(&self) -> &[i64] {
224 &self.bundle_capacities
225 }
226
227 pub fn max_capacity_bits(&self) -> u64 {
229 crate::types::max_numeric_magnitude_bits(self.bundle_capacities.iter().copied())
230 }
231
232 pub fn requirement(&self) -> i64 {
234 self.requirement
235 }
236
237 pub fn num_vertices(&self) -> usize {
239 self.graph.num_vertices()
240 }
241
242 pub fn num_arcs(&self) -> usize {
244 self.graph.num_arcs()
245 }
246
247 pub fn num_bundles(&self) -> usize {
249 self.bundles.len()
250 }
251
252 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;