problemreductions/models/graph/
minimum_cost_maximum_flow.rs1use crate::registry::{ConstructionError, FieldInfo, ProblemSchemaEntry};
43use crate::topology::DirectedGraph;
44use crate::traits::Problem;
45use serde::{Deserialize, Serialize};
46
47inventory::submit! {
48 ProblemSchemaEntry {
49 name: "MinimumCostMaximumFlow",
50 display_name: "Minimum-Cost Maximum-Flow",
51 aliases: &["MCMF"],
52 dimensions: &[],
53 category: crate::registry::ProblemCategory::Graph,
54 module_path: module_path!(),
55 description: "Integral flow that lexicographically maximizes value then minimizes total arc cost",
56 fields: &[
57 FieldInfo { name: "graph", type_name: "DirectedGraph", description: "Directed graph G = (V, A)" },
58 FieldInfo { name: "source", type_name: "usize", description: "Source vertex s" },
59 FieldInfo { name: "sink", type_name: "usize", description: "Sink vertex t" },
60 FieldInfo { name: "capacities", type_name: "Vec<i64>", description: "Arc capacity c(a) in graph arc order (non-negative)" },
61 FieldInfo { name: "costs", type_name: "Vec<i64>", description: "Arc cost cost(a) in graph arc order (non-negative)" },
62 ],
63 }
64}
65
66#[derive(Debug, Clone, Serialize)]
97pub struct MinimumCostMaximumFlow {
98 graph: DirectedGraph,
100 source: usize,
102 sink: usize,
104 capacities: Vec<i64>,
106 costs: Vec<i64>,
108}
109
110#[derive(Deserialize)]
111struct MinimumCostMaximumFlowSerde {
112 graph: DirectedGraph,
113 source: usize,
114 sink: usize,
115 capacities: Vec<i64>,
116 costs: Vec<i64>,
117}
118
119impl TryFrom<MinimumCostMaximumFlowSerde> for MinimumCostMaximumFlow {
120 type Error = ConstructionError;
121
122 fn try_from(value: MinimumCostMaximumFlowSerde) -> Result<Self, Self::Error> {
123 Self::try_new(
124 value.graph,
125 value.source,
126 value.sink,
127 value.capacities,
128 value.costs,
129 )
130 }
131}
132
133impl<'de> Deserialize<'de> for MinimumCostMaximumFlow {
134 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
135 where
136 D: serde::Deserializer<'de>,
137 {
138 MinimumCostMaximumFlowSerde::deserialize(deserializer)?
139 .try_into()
140 .map_err(serde::de::Error::custom)
141 }
142}
143
144impl MinimumCostMaximumFlow {
145 pub fn new(
158 graph: DirectedGraph,
159 source: usize,
160 sink: usize,
161 capacities: Vec<i64>,
162 costs: Vec<i64>,
163 ) -> Self {
164 Self::try_new(graph, source, sink, capacities, costs)
165 .unwrap_or_else(|error| panic!("{error}"))
166 }
167
168 fn try_new(
169 graph: DirectedGraph,
170 source: usize,
171 sink: usize,
172 capacities: Vec<i64>,
173 costs: Vec<i64>,
174 ) -> Result<Self, ConstructionError> {
175 let n = graph.num_vertices();
176 let m = graph.num_arcs();
177 if capacities.len() != m {
178 return Err(format!(
179 "capacities length ({}) must match num_arcs ({m})",
180 capacities.len()
181 )
182 .into());
183 }
184 if costs.len() != m {
185 return Err(format!("costs length ({}) must match num_arcs ({m})", costs.len()).into());
186 }
187 if source >= n {
188 return Err(format!("source ({source}) >= num_vertices ({n})").into());
189 }
190 if sink >= n {
191 return Err(format!("sink ({sink}) >= num_vertices ({n})").into());
192 }
193 if source == sink {
194 return Err("source and sink must be distinct".into());
195 }
196 if let Some((index, capacity)) = capacities
197 .iter()
198 .enumerate()
199 .find(|(_, capacity)| **capacity < 0)
200 {
201 return Err(format!("capacity[{index}] = {capacity} is negative").into());
202 }
203 if let Some((index, cost)) = costs.iter().enumerate().find(|(_, cost)| **cost < 0) {
204 return Err(format!("cost[{index}] = {cost} is negative").into());
205 }
206 Ok(Self {
207 graph,
208 source,
209 sink,
210 capacities,
211 costs,
212 })
213 }
214
215 pub fn graph(&self) -> &DirectedGraph {
217 &self.graph
218 }
219
220 pub fn source(&self) -> usize {
222 self.source
223 }
224
225 pub fn sink(&self) -> usize {
227 self.sink
228 }
229
230 pub fn capacities(&self) -> &[i64] {
232 &self.capacities
233 }
234
235 pub fn costs(&self) -> &[i64] {
237 &self.costs
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 is_feasible(&self, config: &[usize]) -> Result<bool, crate::traits::EvaluationError> {
257 let m = self.graph.num_arcs();
258 if config.len() != m {
259 return Ok(false);
260 }
261 for (flow, cap) in config.iter().zip(self.capacities.iter()) {
263 let flow = i64::try_from(*flow).map_err(|_| {
264 crate::traits::EvaluationError::IntegerOverflow(
265 "converting maximum-flow configuration value".to_string(),
266 )
267 })?;
268 if flow > *cap {
269 return Ok(false);
270 }
271 }
272 let n = self.graph.num_vertices();
274 let mut balance = vec![0_i64; n];
275 for (a, &(u, v)) in self.graph.arcs().iter().enumerate() {
276 let flow = i64::try_from(config[a]).map_err(|_| {
277 crate::traits::EvaluationError::IntegerOverflow(
278 "converting maximum-flow configuration value".to_string(),
279 )
280 })?;
281 balance[u] = balance[u].checked_sub(flow).ok_or_else(|| {
282 crate::traits::EvaluationError::IntegerOverflow(
283 "computing maximum-flow vertex balance".to_string(),
284 )
285 })?;
286 balance[v] = balance[v].checked_add(flow).ok_or_else(|| {
287 crate::traits::EvaluationError::IntegerOverflow(
288 "computing maximum-flow vertex balance".to_string(),
289 )
290 })?;
291 }
292 for (v, &bal) in balance.iter().enumerate() {
293 if v != self.source && v != self.sink && bal != 0 {
294 return Ok(false);
295 }
296 }
297 Ok(true)
298 }
299
300 pub fn flow_value(&self, config: &[usize]) -> Result<i64, crate::traits::EvaluationError> {
304 let mut net_out: i64 = 0;
305 for (a, &(u, v)) in self.graph.arcs().iter().enumerate() {
306 let f = i64::try_from(config[a]).map_err(|_| {
307 crate::traits::EvaluationError::IntegerOverflow(
308 "converting maximum-flow configuration value".to_string(),
309 )
310 })?;
311 if u == self.source {
312 net_out = net_out.checked_add(f).ok_or_else(|| {
313 crate::traits::EvaluationError::IntegerOverflow(
314 "computing maximum-flow value".to_string(),
315 )
316 })?;
317 }
318 if v == self.source {
319 net_out = net_out.checked_sub(f).ok_or_else(|| {
320 crate::traits::EvaluationError::IntegerOverflow(
321 "computing maximum-flow value".to_string(),
322 )
323 })?;
324 }
325 }
326 Ok(net_out)
327 }
328
329 pub fn total_cost(&self, config: &[usize]) -> Result<i64, crate::traits::EvaluationError> {
331 let mut total = 0_i64;
332 for (&flow, &cost) in config.iter().zip(self.costs.iter()) {
333 let flow = i64::try_from(flow).map_err(|_| {
334 crate::traits::EvaluationError::IntegerOverflow(
335 "converting maximum-flow configuration value".to_string(),
336 )
337 })?;
338 let term = flow.checked_mul(cost).ok_or_else(|| {
339 crate::traits::EvaluationError::IntegerOverflow(
340 "multiplying maximum-flow arc cost".to_string(),
341 )
342 })?;
343 total = total.checked_add(term).ok_or_else(|| {
344 crate::traits::EvaluationError::IntegerOverflow(
345 "summing maximum-flow costs".to_string(),
346 )
347 })?;
348 }
349 Ok(total)
350 }
351
352 fn max_possible_flow(&self) -> Result<i64, crate::traits::EvaluationError> {
355 self.capacities.iter().try_fold(0_i64, |total, &capacity| {
356 total.checked_add(capacity).ok_or_else(|| {
357 crate::traits::EvaluationError::IntegerOverflow(
358 "summing maximum-flow capacities".to_string(),
359 )
360 })
361 })
362 }
363
364 fn cost_multiplier(&self) -> Result<i64, crate::traits::EvaluationError> {
368 let mut total = 0_i64;
369 for (&capacity, &cost) in self.capacities.iter().zip(self.costs.iter()) {
370 let term = capacity.checked_mul(cost).ok_or_else(|| {
371 crate::traits::EvaluationError::IntegerOverflow(
372 "multiplying maximum-flow capacity by cost".to_string(),
373 )
374 })?;
375 total = total.checked_add(term).ok_or_else(|| {
376 crate::traits::EvaluationError::IntegerOverflow(
377 "summing maximum-flow cost bounds".to_string(),
378 )
379 })?;
380 }
381 total.checked_add(1).ok_or_else(|| {
382 crate::traits::EvaluationError::IntegerOverflow(
383 "forming maximum-flow cost multiplier".to_string(),
384 )
385 })
386 }
387}
388
389impl Problem for MinimumCostMaximumFlow {
390 const NAME: &'static str = "MinimumCostMaximumFlow";
391 type Solution = Vec<usize>;
392 type Value = crate::types::Min<i64>;
393
394 crate::problem_parameters![("num_arcs", num_arcs), ("num_vertices", num_vertices),];
395
396 fn evaluate(
397 &self,
398 config: &Self::Solution,
399 ) -> Result<crate::types::Min<i64>, crate::traits::EvaluationError> {
400 if config.len() != self.graph.num_arcs() {
401 return Err(crate::traits::EvaluationError::InvalidConfiguration(
402 "flow vector length does not match the graph arcs".into(),
403 ));
404 }
405 Ok({
406 if !self.is_feasible(config)? {
407 return Ok(crate::types::Min(None));
408 }
409 let m = self.cost_multiplier()?;
410 let value = self.flow_value(config)?;
411 let cost = self.total_cost(config)?;
412 let bound = self.max_possible_flow()?;
413 let remaining = bound.checked_sub(value).ok_or_else(|| {
415 crate::traits::EvaluationError::IntegerOverflow(
416 "computing maximum-flow objective gap".to_string(),
417 )
418 })?;
419 let penalty = m.checked_mul(remaining).ok_or_else(|| {
420 crate::traits::EvaluationError::IntegerOverflow(
421 "multiplying maximum-flow objective penalty".to_string(),
422 )
423 })?;
424 let score = penalty.checked_add(cost).ok_or_else(|| {
425 crate::traits::EvaluationError::IntegerOverflow(
426 "summing maximum-flow objective".to_string(),
427 )
428 })?;
429 crate::types::Min(Some(score))
430 })
431 }
432
433 fn variant() -> Vec<(&'static str, &'static str)> {
434 crate::variant_params![]
435 }
436}
437
438impl crate::solvers::BruteForceProblem for MinimumCostMaximumFlow {
439 fn dimensions(&self) -> Vec<usize> {
440 self.capacities.iter().map(|&c| (c as usize) + 1).collect()
441 }
442}
443
444crate::declare_variants! {
445 default MinimumCostMaximumFlow => "(num_vertices + num_arcs)^6",
446}
447
448crate::register_brute_force! {
449 MinimumCostMaximumFlow,
450}
451
452#[cfg(feature = "example-db")]
453pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
454 let problem = MinimumCostMaximumFlow::new(
455 crate::topology::DirectedGraph::new(4, vec![(0, 1), (0, 2), (1, 2), (1, 3), (2, 3)]),
456 0,
457 3,
458 vec![2, 1, 1, 1, 2],
459 vec![1, 0, 0, 1, 2],
460 );
461 let optimal_config = vec![2, 1, 1, 1, 2];
468 let optimal_value = problem
469 .evaluate(&optimal_config)
470 .expect("canonical example evaluation must succeed");
471 let scalar = match optimal_value {
472 crate::types::Min(Some(v)) => v,
473 crate::types::Min(None) => panic!("canonical example must be feasible"),
474 };
475 vec![crate::example_db::specs::ModelExampleSpec {
476 id: "minimum_cost_maximum_flow",
477 instance: Box::new(problem),
478 optimal_config: serde_json::to_value(optimal_config)
479 .expect("solution serialization must succeed"),
480 optimal_value: serde_json::json!(scalar),
481 }]
482}
483
484#[cfg(test)]
485#[path = "../../unit_tests/models/graph/minimum_cost_maximum_flow.rs"]
486mod tests;