problemreductions/models/graph/
integral_flow_bundles.rs1use 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#[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 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 pub fn graph(&self) -> &DirectedGraph {
130 &self.graph
131 }
132
133 pub fn source(&self) -> usize {
135 self.source
136 }
137
138 pub fn sink(&self) -> usize {
140 self.sink
141 }
142
143 pub fn bundles(&self) -> &[Vec<usize>] {
145 &self.bundles
146 }
147
148 pub fn bundle_capacities(&self) -> &[u64] {
150 &self.bundle_capacities
151 }
152
153 pub fn requirement(&self) -> u64 {
155 self.requirement
156 }
157
158 pub fn num_vertices(&self) -> usize {
160 self.graph.num_vertices()
161 }
162
163 pub fn num_arcs(&self) -> usize {
165 self.graph.num_arcs()
166 }
167
168 pub fn num_bundles(&self) -> usize {
170 self.bundles.len()
171 }
172
173 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;