problemreductions/models/graph/
directed_two_commodity_integral_flow.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
10use crate::topology::DirectedGraph;
11use crate::traits::Problem;
12use serde::{Deserialize, Serialize};
13
14inventory::submit! {
15 ProblemSchemaEntry {
16 name: "DirectedTwoCommodityIntegralFlow",
17 display_name: "Directed Two-Commodity Integral Flow",
18 aliases: &["D2CIF"],
19 dimensions: &[],
20 category: crate::registry::ProblemCategory::Graph,
21 module_path: module_path!(),
22 description: "Two-commodity integral flow feasibility on a directed graph",
23 fields: &[
24 FieldInfo { name: "graph", type_name: "DirectedGraph", description: "Directed graph G = (V, A)" },
25 FieldInfo { name: "capacities", type_name: "Vec<i64>", description: "Capacity c(a) for each arc" },
26 FieldInfo { name: "source_1", type_name: "usize", description: "Source vertex s_1 for commodity 1" },
27 FieldInfo { name: "sink_1", type_name: "usize", description: "Sink vertex t_1 for commodity 1" },
28 FieldInfo { name: "source_2", type_name: "usize", description: "Source vertex s_2 for commodity 2" },
29 FieldInfo { name: "sink_2", type_name: "usize", description: "Sink vertex t_2 for commodity 2" },
30 FieldInfo { name: "requirement_1", type_name: "i64", description: "Flow requirement R_1 for commodity 1" },
31 FieldInfo { name: "requirement_2", type_name: "i64", description: "Flow requirement R_2 for commodity 2" },
32 ],
33 }
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct DirectedTwoCommodityIntegralFlow {
72 graph: DirectedGraph,
74 capacities: Vec<i64>,
76 source_1: usize,
78 sink_1: usize,
80 source_2: usize,
82 sink_2: usize,
84 requirement_1: i64,
86 requirement_2: i64,
88}
89
90impl DirectedTwoCommodityIntegralFlow {
91 #[allow(clippy::too_many_arguments)]
99 pub fn new(
100 graph: DirectedGraph,
101 capacities: Vec<i64>,
102 source_1: usize,
103 sink_1: usize,
104 source_2: usize,
105 sink_2: usize,
106 requirement_1: i64,
107 requirement_2: i64,
108 ) -> Self {
109 let n = graph.num_vertices();
110 assert_eq!(
111 capacities.len(),
112 graph.num_arcs(),
113 "capacities length must match graph num_arcs"
114 );
115 assert!(
116 capacities.iter().all(|&capacity| capacity >= 0),
117 "capacities must be nonnegative"
118 );
119 assert!(
120 requirement_1 >= 0 && requirement_2 >= 0,
121 "flow requirements must be nonnegative"
122 );
123 assert!(source_1 < n, "source_1 ({source_1}) >= num_vertices ({n})");
124 assert!(sink_1 < n, "sink_1 ({sink_1}) >= num_vertices ({n})");
125 assert!(source_2 < n, "source_2 ({source_2}) >= num_vertices ({n})");
126 assert!(sink_2 < n, "sink_2 ({sink_2}) >= num_vertices ({n})");
127 Self {
128 graph,
129 capacities,
130 source_1,
131 sink_1,
132 source_2,
133 sink_2,
134 requirement_1,
135 requirement_2,
136 }
137 }
138
139 pub fn graph(&self) -> &DirectedGraph {
141 &self.graph
142 }
143
144 pub fn capacities(&self) -> &[i64] {
146 &self.capacities
147 }
148
149 pub fn source_1(&self) -> usize {
151 self.source_1
152 }
153
154 pub fn sink_1(&self) -> usize {
156 self.sink_1
157 }
158
159 pub fn source_2(&self) -> usize {
161 self.source_2
162 }
163
164 pub fn sink_2(&self) -> usize {
166 self.sink_2
167 }
168
169 pub fn requirement_1(&self) -> i64 {
171 self.requirement_1
172 }
173
174 pub fn requirement_2(&self) -> i64 {
176 self.requirement_2
177 }
178
179 pub fn num_vertices(&self) -> usize {
181 self.graph.num_vertices()
182 }
183
184 pub fn num_arcs(&self) -> usize {
186 self.graph.num_arcs()
187 }
188
189 pub fn max_capacity(&self) -> i64 {
191 self.capacities.iter().copied().max().unwrap_or(0)
192 }
193
194 pub fn is_feasible(&self, config: &[usize]) -> Result<bool, crate::traits::EvaluationError> {
198 let m = self.graph.num_arcs();
199 if config.len() != 2 * m {
200 return Ok(false);
201 }
202 let arcs = self.graph.arcs();
203 for a in 0..m {
205 let f1 = i64::try_from(config[a]).map_err(|_| {
206 crate::traits::EvaluationError::IntegerOverflow(
207 "converting first commodity flow to i64".into(),
208 )
209 })?;
210 let f2 = i64::try_from(config[m + a]).map_err(|_| {
211 crate::traits::EvaluationError::IntegerOverflow(
212 "converting second commodity flow to i64".into(),
213 )
214 })?;
215 if f1.checked_add(f2).ok_or_else(|| {
216 crate::traits::EvaluationError::IntegerOverflow(
217 "summing two-commodity arc flow".into(),
218 )
219 })? > self.capacities[a]
220 {
221 return Ok(false);
222 }
223 }
224
225 let n = self.graph.num_vertices();
227 let mut balances = [vec![0_i64; n], vec![0_i64; n]];
228 for (a, &(u, w)) in arcs.iter().enumerate() {
229 let flow_1 = i64::try_from(config[a]).map_err(|_| {
230 crate::traits::EvaluationError::IntegerOverflow(
231 "converting first commodity flow to i64".into(),
232 )
233 })?;
234 let flow_2 = i64::try_from(config[m + a]).map_err(|_| {
235 crate::traits::EvaluationError::IntegerOverflow(
236 "converting second commodity flow to i64".into(),
237 )
238 })?;
239
240 for (commodity, flow) in [(0, flow_1), (1, flow_2)] {
241 balances[commodity][u] =
242 balances[commodity][u].checked_sub(flow).ok_or_else(|| {
243 crate::traits::EvaluationError::IntegerOverflow(
244 "subtracting outgoing commodity flow".into(),
245 )
246 })?;
247 balances[commodity][w] =
248 balances[commodity][w].checked_add(flow).ok_or_else(|| {
249 crate::traits::EvaluationError::IntegerOverflow(
250 "adding incoming commodity flow".into(),
251 )
252 })?;
253 }
254 }
255
256 for (commodity, commodity_balances) in balances.iter().enumerate() {
257 let src = if commodity == 0 {
258 self.source_1
259 } else {
260 self.source_2
261 };
262 for (v, &balance) in commodity_balances.iter().enumerate() {
263 let snk = if commodity == 0 {
264 self.sink_1
265 } else {
266 self.sink_2
267 };
268 if v != src && v != snk && balance != 0 {
269 return Ok(false);
270 }
271 }
272
273 let snk = if commodity == 0 {
274 self.sink_1
275 } else {
276 self.sink_2
277 };
278 let req = if commodity == 0 {
279 self.requirement_1
280 } else {
281 self.requirement_2
282 };
283
284 if commodity_balances[snk] < req {
285 return Ok(false);
286 }
287 }
288
289 Ok(true)
290 }
291}
292
293impl Problem for DirectedTwoCommodityIntegralFlow {
294 const NAME: &'static str = "DirectedTwoCommodityIntegralFlow";
295 type Solution = Vec<usize>;
296 type Value = crate::types::Or;
297
298 crate::problem_parameters![
299 ("max_capacity", max_capacity),
300 ("num_arcs", num_arcs),
301 ("num_vertices", num_vertices),
302 ];
303
304 fn evaluate(
305 &self,
306 config: &Self::Solution,
307 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
308 if config.len() != 2 * self.graph.num_arcs() {
309 return Err(crate::traits::EvaluationError::InvalidConfiguration(
310 "two-commodity flow vector length does not match the graph arcs".into(),
311 ));
312 }
313 Ok(crate::types::Or(self.is_feasible(config)?))
314 }
315
316 fn variant() -> Vec<(&'static str, &'static str)> {
317 crate::variant_params![]
318 }
319}
320
321impl crate::solvers::BruteForceProblem for DirectedTwoCommodityIntegralFlow {
322 fn dimensions(&self) -> Vec<usize> {
323 self.capacities
324 .iter()
325 .chain(self.capacities.iter())
326 .map(|&c| (c as usize) + 1)
327 .collect()
328 }
329}
330
331crate::declare_variants! {
332 default DirectedTwoCommodityIntegralFlow => "(max_capacity + 1)^(2 * num_arcs)",
333}
334
335crate::register_brute_force! {
336 DirectedTwoCommodityIntegralFlow,
337}
338
339#[cfg(feature = "example-db")]
340pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
341 vec![crate::example_db::specs::ModelExampleSpec {
342 id: "directed_two_commodity_integral_flow",
343 instance: Box::new(DirectedTwoCommodityIntegralFlow::new(
344 DirectedGraph::new(
345 6,
346 vec![
347 (0, 2),
348 (0, 3),
349 (1, 2),
350 (1, 3),
351 (2, 4),
352 (2, 5),
353 (3, 4),
354 (3, 5),
355 ],
356 ),
357 vec![1; 8],
358 0,
359 4,
360 1,
361 5,
362 1,
363 1,
364 )),
365 optimal_config: serde_json::json!(vec![1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1]),
366 optimal_value: serde_json::json!(true),
367 }]
368}
369
370#[cfg(test)]
371#[path = "../../unit_tests/models/graph/directed_two_commodity_integral_flow.rs"]
372mod tests;