1use crate::registry::{CreateSpec, ProblemSchemaEntry};
7use crate::topology::{Graph, SimpleGraph};
8use crate::traits::Problem;
9use serde::{Deserialize, Serialize};
10
11inventory::submit! {
12 ProblemSchemaEntry {
13 name: "UndirectedTwoCommodityIntegralFlow",
14 display_name: "Undirected Two-Commodity Integral Flow",
15 aliases: &[],
16 dimensions: &[],
17 category: crate::registry::ProblemCategory::Graph,
18 module_path: module_path!(),
19 description: "Determine whether two integral commodities can satisfy sink demands in an undirected capacitated graph",
20 fields: UndirectedTwoCommodityIntegralFlowCreateSpec::FIELDS,
21 }
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
33#[serde(try_from = "UndirectedTwoCommodityIntegralFlowData")]
34pub struct UndirectedTwoCommodityIntegralFlow {
35 graph: SimpleGraph,
36 capacities: Vec<i64>,
37 source_1: usize,
38 sink_1: usize,
39 source_2: usize,
40 sink_2: usize,
41 requirement_1: i64,
42 requirement_2: i64,
43}
44
45#[derive(Deserialize)]
46struct UndirectedTwoCommodityIntegralFlowData {
47 graph: SimpleGraph,
48 capacities: Vec<i64>,
49 source_1: usize,
50 sink_1: usize,
51 source_2: usize,
52 sink_2: usize,
53 requirement_1: i64,
54 requirement_2: i64,
55}
56
57impl TryFrom<UndirectedTwoCommodityIntegralFlowData> for UndirectedTwoCommodityIntegralFlow {
58 type Error = crate::registry::ConstructionError;
59 fn try_from(data: UndirectedTwoCommodityIntegralFlowData) -> Result<Self, Self::Error> {
60 Self::try_new(
61 data.graph,
62 data.capacities,
63 data.source_1,
64 data.sink_1,
65 data.source_2,
66 data.sink_2,
67 data.requirement_1,
68 data.requirement_2,
69 )
70 }
71}
72
73#[derive(Debug, Deserialize, crate::CreateSpec)]
74struct UndirectedTwoCommodityIntegralFlowCreateSpec {
75 #[create(codec = "edge-list")]
77 graph: Vec<(usize, usize)>,
78 num_vertices: Option<usize>,
80 #[create(codec = "comma-separated")]
82 capacities: Vec<i64>,
83 source_1: usize,
84 sink_1: usize,
85 source_2: usize,
86 sink_2: usize,
87 requirement_1: i64,
88 requirement_2: i64,
89}
90
91impl TryFrom<UndirectedTwoCommodityIntegralFlowCreateSpec> for UndirectedTwoCommodityIntegralFlow {
92 type Error = crate::registry::ConstructionError;
93 fn try_from(spec: UndirectedTwoCommodityIntegralFlowCreateSpec) -> Result<Self, Self::Error> {
94 if spec.graph.is_empty() && spec.num_vertices.is_none() {
95 return Err("num_vertices is required for an empty graph".into());
96 }
97 for &(u, v) in &spec.graph {
98 if u == v {
99 return Err(format!("self-loop {u}-{v} is not allowed").into());
100 }
101 }
102 let inferred = spec
103 .graph
104 .iter()
105 .flat_map(|&(u, v)| [u, v])
106 .max()
107 .map(|v| v.checked_add(1).ok_or("vertex count overflows usize"))
108 .transpose()?
109 .unwrap_or(0);
110 let count = spec.num_vertices.unwrap_or(inferred);
111 if count < inferred {
112 return Err("num_vertices is too small for graph endpoints".into());
113 }
114 Self::try_new(
115 SimpleGraph::new(count, spec.graph),
116 spec.capacities,
117 spec.source_1,
118 spec.sink_1,
119 spec.source_2,
120 spec.sink_2,
121 spec.requirement_1,
122 spec.requirement_2,
123 )
124 }
125}
126
127impl UndirectedTwoCommodityIntegralFlow {
128 #[allow(clippy::too_many_arguments)]
129 pub fn new(
130 graph: SimpleGraph,
131 capacities: Vec<i64>,
132 source_1: usize,
133 sink_1: usize,
134 source_2: usize,
135 sink_2: usize,
136 requirement_1: i64,
137 requirement_2: i64,
138 ) -> Self {
139 Self::try_new(
140 graph,
141 capacities,
142 source_1,
143 sink_1,
144 source_2,
145 sink_2,
146 requirement_1,
147 requirement_2,
148 )
149 .unwrap_or_else(|error| panic!("{error}"))
150 }
151
152 #[allow(clippy::too_many_arguments)]
153 fn try_new(
154 graph: SimpleGraph,
155 capacities: Vec<i64>,
156 source_1: usize,
157 sink_1: usize,
158 source_2: usize,
159 sink_2: usize,
160 requirement_1: i64,
161 requirement_2: i64,
162 ) -> Result<Self, crate::registry::ConstructionError> {
163 if capacities.len() != graph.num_edges() {
164 return Err(crate::registry::ConstructionError::length_mismatch(
165 "capacities",
166 capacities.len(),
167 graph.num_edges(),
168 ));
169 }
170
171 let num_vertices = graph.num_vertices();
172 for (label, vertex) in [
173 ("source_1", source_1),
174 ("sink_1", sink_1),
175 ("source_2", source_2),
176 ("sink_2", sink_2),
177 ] {
178 if vertex >= num_vertices {
179 return Err(
180 format!("{label} must be less than num_vertices ({num_vertices})").into(),
181 );
182 }
183 }
184
185 for &capacity in &capacities {
186 if usize::try_from(capacity)
187 .ok()
188 .and_then(|value| value.checked_add(1))
189 .is_none()
190 {
191 return Err("edge capacities must fit into usize for dims()".into());
192 }
193 }
194
195 Ok(Self {
196 graph,
197 capacities,
198 source_1,
199 sink_1,
200 source_2,
201 sink_2,
202 requirement_1,
203 requirement_2,
204 })
205 }
206
207 pub fn graph(&self) -> &SimpleGraph {
208 &self.graph
209 }
210
211 pub fn capacities(&self) -> &[i64] {
212 &self.capacities
213 }
214
215 pub fn max_capacity_bits(&self) -> u64 {
217 crate::types::max_numeric_magnitude_bits(self.capacities.iter().copied())
218 }
219
220 pub fn source_1(&self) -> usize {
221 self.source_1
222 }
223
224 pub fn sink_1(&self) -> usize {
225 self.sink_1
226 }
227
228 pub fn source_2(&self) -> usize {
229 self.source_2
230 }
231
232 pub fn sink_2(&self) -> usize {
233 self.sink_2
234 }
235
236 pub fn requirement_1(&self) -> i64 {
237 self.requirement_1
238 }
239
240 pub fn requirement_2(&self) -> i64 {
241 self.requirement_2
242 }
243
244 pub fn num_vertices(&self) -> usize {
245 self.graph.num_vertices()
246 }
247
248 pub fn num_edges(&self) -> usize {
249 self.graph.num_edges()
250 }
251
252 pub fn num_conservation_constraints(&self) -> usize {
253 [(self.source_1, self.sink_1), (self.source_2, self.sink_2)]
254 .into_iter()
255 .map(|(source, sink)| self.num_vertices() - if source == sink { 1 } else { 2 })
256 .sum()
257 }
258
259 pub fn is_valid_solution(
260 &self,
261 config: &[usize],
262 ) -> Result<bool, crate::traits::EvaluationError> {
263 Ok(self.evaluate_solution(config)?.0)
264 }
265
266 fn config_len(&self) -> usize {
267 self.num_edges() * 4
268 }
269
270 fn domain_size(capacity: i64) -> usize {
271 usize::try_from(capacity)
272 .ok()
273 .and_then(|value| value.checked_add(1))
274 .expect("capacity already validated to fit into usize")
275 }
276
277 fn edge_flows(&self, config: &[usize], edge_index: usize) -> Option<[usize; 4]> {
278 let start = edge_index.checked_mul(4)?;
279 Some([
280 *config.get(start)?,
281 *config.get(start + 1)?,
282 *config.get(start + 2)?,
283 *config.get(start + 3)?,
284 ])
285 }
286
287 fn flow_pair_for_commodity(flows: [usize; 4], commodity: usize) -> (usize, usize) {
288 match commodity {
289 1 => (flows[0], flows[1]),
290 2 => (flows[2], flows[3]),
291 _ => unreachable!("commodity must be 1 or 2"),
292 }
293 }
294
295 fn commodity_balance(
296 &self,
297 config: &[usize],
298 commodity: usize,
299 vertex: usize,
300 ) -> Result<Option<i64>, crate::traits::EvaluationError> {
301 let mut balance = 0_i64;
302 for (edge_index, (u, v)) in self.graph.edges().into_iter().enumerate() {
303 if u == v {
305 continue;
306 }
307 let Some(flows) = self.edge_flows(config, edge_index) else {
308 return Ok(None);
309 };
310 let (uv, vu) = Self::flow_pair_for_commodity(flows, commodity);
311 let uv = i64::try_from(uv).map_err(|_| {
312 crate::traits::EvaluationError::IntegerOverflow(
313 "converting forward commodity flow to i64".into(),
314 )
315 })?;
316 let vu = i64::try_from(vu).map_err(|_| {
317 crate::traits::EvaluationError::IntegerOverflow(
318 "converting reverse commodity flow to i64".into(),
319 )
320 })?;
321
322 if vertex == u {
323 balance = balance.checked_sub(uv).ok_or_else(|| {
324 crate::traits::EvaluationError::IntegerOverflow(
325 "subtracting forward commodity flow".into(),
326 )
327 })?;
328 balance = balance.checked_add(vu).ok_or_else(|| {
329 crate::traits::EvaluationError::IntegerOverflow(
330 "adding reverse commodity flow".into(),
331 )
332 })?;
333 } else if vertex == v {
334 balance = balance.checked_add(uv).ok_or_else(|| {
335 crate::traits::EvaluationError::IntegerOverflow(
336 "adding forward commodity flow".into(),
337 )
338 })?;
339 balance = balance.checked_sub(vu).ok_or_else(|| {
340 crate::traits::EvaluationError::IntegerOverflow(
341 "subtracting reverse commodity flow".into(),
342 )
343 })?;
344 }
345 }
346 Ok(Some(balance))
347 }
348
349 fn net_flow_into_sink(
350 &self,
351 config: &[usize],
352 commodity: usize,
353 ) -> Result<Option<i64>, crate::traits::EvaluationError> {
354 let sink = match commodity {
355 1 => self.sink_1,
356 2 => self.sink_2,
357 _ => unreachable!("commodity must be 1 or 2"),
358 };
359 self.commodity_balance(config, commodity, sink)
360 }
361
362 fn evaluate_solution(
363 &self,
364 config: &[usize],
365 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
366 if config.len() != self.config_len() {
367 return Err(crate::traits::EvaluationError::InvalidConfiguration(
368 "flow representation length does not match the graph".into(),
369 ));
370 }
371
372 for (edge_index, &capacity) in self.capacities.iter().enumerate() {
373 let Some(flows) = self.edge_flows(config, edge_index) else {
374 return Ok(crate::types::Or(false));
375 };
376
377 if flows
378 .iter()
379 .any(|&value| i64::try_from(value).map_or(true, |value| value > capacity))
380 {
381 return Ok(crate::types::Or(false));
382 }
383 if flows[0] > 0 && flows[1] > 0 || flows[2] > 0 && flows[3] > 0 {
384 return Ok(crate::types::Or(false));
385 }
386
387 let commodity_1 = i64::try_from(std::cmp::max(flows[0], flows[1]))
388 .expect("flow values already validated against i64 capacities");
389 let commodity_2 = i64::try_from(std::cmp::max(flows[2], flows[3]))
390 .expect("flow values already validated against i64 capacities");
391 let shared = commodity_1.checked_add(commodity_2).ok_or_else(|| {
392 crate::traits::EvaluationError::IntegerOverflow(
393 "summing two commodities on an undirected edge".into(),
394 )
395 })?;
396 if shared > capacity {
397 return Ok(crate::types::Or(false));
398 }
399 }
400
401 for (commodity, source, sink) in [
402 (1, self.source_1, self.sink_1),
403 (2, self.source_2, self.sink_2),
404 ] {
405 for vertex in 0..self.num_vertices() {
406 if vertex != source
407 && vertex != sink
408 && self.commodity_balance(config, commodity, vertex)? != Some(0)
409 {
410 return Ok(crate::types::Or(false));
411 }
412 }
413 }
414
415 Ok(crate::types::Or(
416 self.net_flow_into_sink(config, 1)?
417 .is_some_and(|flow| flow >= self.requirement_1)
418 && self
419 .net_flow_into_sink(config, 2)?
420 .is_some_and(|flow| flow >= self.requirement_2),
421 ))
422 }
423}
424
425impl Problem for UndirectedTwoCommodityIntegralFlow {
426 const NAME: &'static str = "UndirectedTwoCommodityIntegralFlow";
427 type Solution = Vec<usize>;
428 type Value = crate::types::Or;
429
430 crate::problem_parameters![
431 ("max_capacity_bits", max_capacity_bits),
432 ("num_edges", num_edges),
433 ("num_conservation_constraints", num_conservation_constraints),
434 ("num_vertices", num_vertices),
435 ];
436
437 fn variant() -> Vec<(&'static str, &'static str)> {
438 crate::variant_params![]
439 }
440
441 fn evaluate(
442 &self,
443 config: &Self::Solution,
444 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
445 self.evaluate_solution(config)
446 }
447}
448
449impl crate::solvers::BruteForceProblem for UndirectedTwoCommodityIntegralFlow {
450 fn dimensions(&self) -> Vec<usize> {
451 self.capacities
452 .iter()
453 .flat_map(|&capacity| {
454 let domain = Self::domain_size(capacity);
455 std::iter::repeat_n(domain, 4)
456 })
457 .collect()
458 }
459}
460
461crate::declare_variants! {
462 default UndirectedTwoCommodityIntegralFlow => "5^num_edges" create UndirectedTwoCommodityIntegralFlowCreateSpec,
463}
464
465crate::register_brute_force! {
466 UndirectedTwoCommodityIntegralFlow,
467}
468
469#[cfg(feature = "example-db")]
470pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
471 vec![crate::example_db::specs::ModelExampleSpec {
472 id: "undirected_two_commodity_integral_flow",
473 instance: Box::new(UndirectedTwoCommodityIntegralFlow::new(
474 SimpleGraph::new(4, vec![(0, 2), (1, 2), (2, 3)]),
475 vec![1, 1, 2],
476 0,
477 3,
478 1,
479 3,
480 1,
481 1,
482 )),
483 optimal_config: serde_json::json!(vec![1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0]),
484 optimal_value: serde_json::json!(true),
485 }]
486}
487
488#[cfg(test)]
489#[path = "../../unit_tests/models/graph/undirected_two_commodity_integral_flow.rs"]
490mod tests;