1use 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)]
71#[serde(try_from = "DirectedTwoCommodityIntegralFlowData")]
72pub struct DirectedTwoCommodityIntegralFlow {
73 graph: DirectedGraph,
75 capacities: Vec<i64>,
77 source_1: usize,
79 sink_1: usize,
81 source_2: usize,
83 sink_2: usize,
85 requirement_1: i64,
87 requirement_2: i64,
89}
90
91#[derive(Deserialize)]
92struct DirectedTwoCommodityIntegralFlowData {
93 graph: DirectedGraph,
94 capacities: Vec<i64>,
95 source_1: usize,
96 sink_1: usize,
97 source_2: usize,
98 sink_2: usize,
99 requirement_1: i64,
100 requirement_2: i64,
101}
102
103impl TryFrom<DirectedTwoCommodityIntegralFlowData> for DirectedTwoCommodityIntegralFlow {
104 type Error = crate::registry::ConstructionError;
105 fn try_from(data: DirectedTwoCommodityIntegralFlowData) -> Result<Self, Self::Error> {
106 Self::try_new(
107 data.graph,
108 data.capacities,
109 data.source_1,
110 data.sink_1,
111 data.source_2,
112 data.sink_2,
113 data.requirement_1,
114 data.requirement_2,
115 )
116 }
117}
118
119impl DirectedTwoCommodityIntegralFlow {
120 #[allow(clippy::too_many_arguments)]
128 pub fn new(
129 graph: DirectedGraph,
130 capacities: Vec<i64>,
131 source_1: usize,
132 sink_1: usize,
133 source_2: usize,
134 sink_2: usize,
135 requirement_1: i64,
136 requirement_2: i64,
137 ) -> Self {
138 Self::try_new(
139 graph,
140 capacities,
141 source_1,
142 sink_1,
143 source_2,
144 sink_2,
145 requirement_1,
146 requirement_2,
147 )
148 .unwrap_or_else(|error| panic!("{error}"))
149 }
150
151 #[allow(clippy::too_many_arguments)]
152 fn try_new(
153 graph: DirectedGraph,
154 capacities: Vec<i64>,
155 source_1: usize,
156 sink_1: usize,
157 source_2: usize,
158 sink_2: usize,
159 requirement_1: i64,
160 requirement_2: i64,
161 ) -> Result<Self, crate::registry::ConstructionError> {
162 let n = graph.num_vertices();
163 if capacities.len() != graph.num_arcs() {
164 return Err(crate::registry::ConstructionError::length_mismatch(
165 "capacities",
166 capacities.len(),
167 graph.num_arcs(),
168 ));
169 }
170 if capacities.iter().any(|&capacity| capacity < 0) {
171 return Err("capacities must be nonnegative".into());
172 }
173 if requirement_1 < 0 || requirement_2 < 0 {
174 return Err("flow requirements must be nonnegative".into());
175 }
176 if source_1 >= n {
177 return Err(format!("source_1 ({source_1}) >= num_vertices ({n})").into());
178 }
179 if sink_1 >= n {
180 return Err(format!("sink_1 ({sink_1}) >= num_vertices ({n})").into());
181 }
182 if source_2 >= n {
183 return Err(format!("source_2 ({source_2}) >= num_vertices ({n})").into());
184 }
185 if sink_2 >= n {
186 return Err(format!("sink_2 ({sink_2}) >= num_vertices ({n})").into());
187 }
188 Ok(Self {
189 graph,
190 capacities,
191 source_1,
192 sink_1,
193 source_2,
194 sink_2,
195 requirement_1,
196 requirement_2,
197 })
198 }
199
200 pub fn graph(&self) -> &DirectedGraph {
202 &self.graph
203 }
204
205 pub fn capacities(&self) -> &[i64] {
207 &self.capacities
208 }
209
210 pub fn source_1(&self) -> usize {
212 self.source_1
213 }
214
215 pub fn sink_1(&self) -> usize {
217 self.sink_1
218 }
219
220 pub fn source_2(&self) -> usize {
222 self.source_2
223 }
224
225 pub fn sink_2(&self) -> usize {
227 self.sink_2
228 }
229
230 pub fn requirement_1(&self) -> i64 {
232 self.requirement_1
233 }
234
235 pub fn requirement_2(&self) -> i64 {
237 self.requirement_2
238 }
239
240 pub fn num_vertices(&self) -> usize {
242 self.graph.num_vertices()
243 }
244
245 pub fn num_arcs(&self) -> usize {
247 self.graph.num_arcs()
248 }
249
250 pub fn max_capacity(&self) -> i64 {
252 self.capacities.iter().copied().max().unwrap_or(0)
253 }
254
255 pub fn is_feasible(&self, config: &[usize]) -> Result<bool, crate::traits::EvaluationError> {
259 let m = self.graph.num_arcs();
260 if config.len() != 2 * m {
261 return Ok(false);
262 }
263 let arcs = self.graph.arcs();
264 for a in 0..m {
266 let f1 = i64::try_from(config[a]).map_err(|_| {
267 crate::traits::EvaluationError::IntegerOverflow(
268 "converting first commodity flow to i64".into(),
269 )
270 })?;
271 let f2 = i64::try_from(config[m + a]).map_err(|_| {
272 crate::traits::EvaluationError::IntegerOverflow(
273 "converting second commodity flow to i64".into(),
274 )
275 })?;
276 if f1.checked_add(f2).ok_or_else(|| {
277 crate::traits::EvaluationError::IntegerOverflow(
278 "summing two-commodity arc flow".into(),
279 )
280 })? > self.capacities[a]
281 {
282 return Ok(false);
283 }
284 }
285
286 let n = self.graph.num_vertices();
288 let mut balances = [vec![0_i64; n], vec![0_i64; n]];
289 for (a, &(u, w)) in arcs.iter().enumerate() {
290 let flow_1 = i64::try_from(config[a]).map_err(|_| {
291 crate::traits::EvaluationError::IntegerOverflow(
292 "converting first commodity flow to i64".into(),
293 )
294 })?;
295 let flow_2 = i64::try_from(config[m + a]).map_err(|_| {
296 crate::traits::EvaluationError::IntegerOverflow(
297 "converting second commodity flow to i64".into(),
298 )
299 })?;
300
301 for (commodity, flow) in [(0, flow_1), (1, flow_2)] {
302 balances[commodity][u] =
303 balances[commodity][u].checked_sub(flow).ok_or_else(|| {
304 crate::traits::EvaluationError::IntegerOverflow(
305 "subtracting outgoing commodity flow".into(),
306 )
307 })?;
308 balances[commodity][w] =
309 balances[commodity][w].checked_add(flow).ok_or_else(|| {
310 crate::traits::EvaluationError::IntegerOverflow(
311 "adding incoming commodity flow".into(),
312 )
313 })?;
314 }
315 }
316
317 for (commodity, commodity_balances) in balances.iter().enumerate() {
318 let src = if commodity == 0 {
319 self.source_1
320 } else {
321 self.source_2
322 };
323 for (v, &balance) in commodity_balances.iter().enumerate() {
324 let snk = if commodity == 0 {
325 self.sink_1
326 } else {
327 self.sink_2
328 };
329 if v != src && v != snk && balance != 0 {
330 return Ok(false);
331 }
332 }
333
334 let snk = if commodity == 0 {
335 self.sink_1
336 } else {
337 self.sink_2
338 };
339 let req = if commodity == 0 {
340 self.requirement_1
341 } else {
342 self.requirement_2
343 };
344
345 if commodity_balances[snk] < req {
346 return Ok(false);
347 }
348 }
349
350 Ok(true)
351 }
352}
353
354impl Problem for DirectedTwoCommodityIntegralFlow {
355 const NAME: &'static str = "DirectedTwoCommodityIntegralFlow";
356 type Solution = Vec<usize>;
357 type Value = crate::types::Or;
358
359 crate::problem_parameters![
360 ("max_capacity", max_capacity),
361 ("num_arcs", num_arcs),
362 ("num_vertices", num_vertices),
363 ];
364
365 fn evaluate(
366 &self,
367 config: &Self::Solution,
368 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
369 if config.len() != 2 * self.graph.num_arcs() {
370 return Err(crate::traits::EvaluationError::InvalidConfiguration(
371 "two-commodity flow vector length does not match the graph arcs".into(),
372 ));
373 }
374 Ok(crate::types::Or(self.is_feasible(config)?))
375 }
376
377 fn variant() -> Vec<(&'static str, &'static str)> {
378 crate::variant_params![]
379 }
380}
381
382impl crate::solvers::BruteForceProblem for DirectedTwoCommodityIntegralFlow {
383 fn dimensions(&self) -> Vec<usize> {
384 self.capacities
385 .iter()
386 .chain(self.capacities.iter())
387 .map(|&c| (c as usize) + 1)
388 .collect()
389 }
390}
391
392crate::declare_variants! {
393 default DirectedTwoCommodityIntegralFlow => "(max_capacity + 1)^(2 * num_arcs)",
394}
395
396crate::register_brute_force! {
397 DirectedTwoCommodityIntegralFlow,
398}
399
400#[cfg(feature = "example-db")]
401pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
402 vec![crate::example_db::specs::ModelExampleSpec {
403 id: "directed_two_commodity_integral_flow",
404 instance: Box::new(DirectedTwoCommodityIntegralFlow::new(
405 DirectedGraph::new(
406 6,
407 vec![
408 (0, 2),
409 (0, 3),
410 (1, 2),
411 (1, 3),
412 (2, 4),
413 (2, 5),
414 (3, 4),
415 (3, 5),
416 ],
417 ),
418 vec![1; 8],
419 0,
420 4,
421 1,
422 5,
423 1,
424 1,
425 )),
426 optimal_config: serde_json::json!(vec![1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1]),
427 optimal_value: serde_json::json!(true),
428 }]
429}
430
431#[cfg(test)]
432#[path = "../../unit_tests/models/graph/directed_two_commodity_integral_flow.rs"]
433mod tests;