problemreductions/models/graph/
minimum_edge_cost_flow.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
12use crate::topology::DirectedGraph;
13use crate::traits::Problem;
14use serde::{Deserialize, Serialize};
15
16inventory::submit! {
17 ProblemSchemaEntry {
18 name: "MinimumEdgeCostFlow",
19 display_name: "Minimum Edge-Cost Flow",
20 aliases: &["MECF"],
21 dimensions: &[],
22 category: crate::registry::ProblemCategory::Graph,
23 module_path: module_path!(),
24 description: "Integral flow minimizing the number of arcs with nonzero flow (weighted by price)",
25 fields: &[
26 FieldInfo { name: "graph", type_name: "DirectedGraph", description: "Directed graph G = (V, A)" },
27 FieldInfo { name: "prices", type_name: "Vec<i64>", description: "Price p(a) for each arc" },
28 FieldInfo { name: "capacities", type_name: "Vec<i64>", description: "Capacity c(a) for each arc" },
29 FieldInfo { name: "source", type_name: "usize", description: "Source vertex s" },
30 FieldInfo { name: "sink", type_name: "usize", description: "Sink vertex t" },
31 FieldInfo { name: "required_flow", type_name: "i64", description: "Flow requirement R" },
32 ],
33 }
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct MinimumEdgeCostFlow {
70 graph: DirectedGraph,
72 prices: Vec<i64>,
74 capacities: Vec<i64>,
76 source: usize,
78 sink: usize,
80 required_flow: i64,
82}
83
84impl MinimumEdgeCostFlow {
85 pub fn new(
106 graph: DirectedGraph,
107 prices: Vec<i64>,
108 capacities: Vec<i64>,
109 source: usize,
110 sink: usize,
111 required_flow: i64,
112 ) -> Self {
113 let n = graph.num_vertices();
114 let m = graph.num_arcs();
115 assert_eq!(
116 prices.len(),
117 m,
118 "prices length ({}) must match num_arcs ({m})",
119 prices.len()
120 );
121 assert_eq!(
122 capacities.len(),
123 m,
124 "capacities length ({}) must match num_arcs ({m})",
125 capacities.len()
126 );
127 assert!(source < n, "source ({source}) >= num_vertices ({n})");
128 assert!(sink < n, "sink ({sink}) >= num_vertices ({n})");
129 assert_ne!(source, sink, "source and sink must be distinct");
130 for (i, &c) in capacities.iter().enumerate() {
131 assert!(c >= 0, "capacity[{i}] = {c} is negative");
132 }
133 Self {
134 graph,
135 prices,
136 capacities,
137 source,
138 sink,
139 required_flow,
140 }
141 }
142
143 pub fn graph(&self) -> &DirectedGraph {
145 &self.graph
146 }
147
148 pub fn prices(&self) -> &[i64] {
150 &self.prices
151 }
152
153 pub fn capacities(&self) -> &[i64] {
155 &self.capacities
156 }
157
158 pub fn source(&self) -> usize {
160 self.source
161 }
162
163 pub fn sink(&self) -> usize {
165 self.sink
166 }
167
168 pub fn required_flow(&self) -> i64 {
170 self.required_flow
171 }
172
173 pub fn num_vertices(&self) -> usize {
175 self.graph.num_vertices()
176 }
177
178 pub fn num_edges(&self) -> usize {
180 self.graph.num_arcs()
181 }
182
183 pub fn max_capacity(&self) -> i64 {
185 self.capacities.iter().copied().max().unwrap_or(0)
186 }
187
188 pub fn edges(&self) -> Vec<(usize, usize)> {
190 self.graph.arcs()
191 }
192
193 pub fn is_feasible(&self, config: &[usize]) -> Result<bool, crate::traits::EvaluationError> {
200 let m = self.graph.num_arcs();
201 if config.len() != m {
202 return Ok(false);
203 }
204 let arcs = self.graph.arcs();
205
206 for (flow, cap) in config.iter().zip(self.capacities.iter()) {
208 let flow = i64::try_from(*flow).map_err(|_| {
209 crate::traits::EvaluationError::IntegerOverflow(
210 "converting edge-cost flow configuration value".to_string(),
211 )
212 })?;
213 if flow > *cap {
214 return Ok(false);
215 }
216 }
217
218 let n = self.graph.num_vertices();
220 let mut balance = vec![0_i64; n];
221 for (a, &(u, v)) in arcs.iter().enumerate() {
222 let flow = i64::try_from(config[a]).map_err(|_| {
223 crate::traits::EvaluationError::IntegerOverflow(
224 "converting edge-cost flow configuration value".to_string(),
225 )
226 })?;
227 balance[u] = balance[u].checked_sub(flow).ok_or_else(|| {
228 crate::traits::EvaluationError::IntegerOverflow(
229 "computing edge-cost flow balance".to_string(),
230 )
231 })?;
232 balance[v] = balance[v].checked_add(flow).ok_or_else(|| {
233 crate::traits::EvaluationError::IntegerOverflow(
234 "computing edge-cost flow balance".to_string(),
235 )
236 })?;
237 }
238
239 for (v, &bal) in balance.iter().enumerate() {
240 if v != self.source && v != self.sink && bal != 0 {
241 return Ok(false);
242 }
243 }
244
245 if balance[self.sink] < self.required_flow {
247 return Ok(false);
248 }
249
250 Ok(true)
251 }
252
253 pub fn edge_cost(&self, config: &[usize]) -> Result<i64, crate::traits::EvaluationError> {
256 config
257 .iter()
258 .enumerate()
259 .filter(|(_, &flow)| flow > 0)
260 .try_fold(0_i64, |total, (arc, _)| {
261 total.checked_add(self.prices[arc]).ok_or_else(|| {
262 crate::traits::EvaluationError::IntegerOverflow(
263 "summing selected edge prices".to_string(),
264 )
265 })
266 })
267 }
268}
269
270impl Problem for MinimumEdgeCostFlow {
271 const NAME: &'static str = "MinimumEdgeCostFlow";
272 type Solution = Vec<usize>;
273 type Value = crate::types::Min<i64>;
274
275 crate::problem_parameters![
276 ("max_capacity", max_capacity),
277 ("num_edges", num_edges),
278 ("num_vertices", num_vertices),
279 ];
280
281 fn evaluate(
282 &self,
283 config: &Self::Solution,
284 ) -> Result<crate::types::Min<i64>, crate::traits::EvaluationError> {
285 if config.len() != self.graph.num_arcs() {
286 return Err(crate::traits::EvaluationError::InvalidConfiguration(
287 "flow vector length does not match the graph arcs".into(),
288 ));
289 }
290 Ok({
291 if self.is_feasible(config)? {
292 crate::types::Min(Some(self.edge_cost(config)?))
293 } else {
294 crate::types::Min(None)
295 }
296 })
297 }
298
299 fn variant() -> Vec<(&'static str, &'static str)> {
300 crate::variant_params![]
301 }
302}
303
304impl crate::solvers::BruteForceProblem for MinimumEdgeCostFlow {
305 fn dimensions(&self) -> Vec<usize> {
306 self.capacities.iter().map(|&c| (c as usize) + 1).collect()
307 }
308}
309
310crate::declare_variants! {
311 default MinimumEdgeCostFlow => "(max_capacity + 1)^num_edges",
312}
313
314crate::register_brute_force! {
315 MinimumEdgeCostFlow,
316}
317
318#[cfg(feature = "example-db")]
319pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
320 vec![crate::example_db::specs::ModelExampleSpec {
321 id: "minimum_edge_cost_flow",
322 instance: Box::new(MinimumEdgeCostFlow::new(
323 crate::topology::DirectedGraph::new(
324 5,
325 vec![(0, 1), (0, 2), (0, 3), (1, 4), (2, 4), (3, 4)],
326 ),
327 vec![3, 1, 2, 0, 0, 0], vec![2, 2, 2, 2, 2, 2], 0,
330 4,
331 3,
332 )),
333 optimal_config: serde_json::json!(vec![0, 1, 2, 0, 1, 2]),
336 optimal_value: serde_json::json!(3),
337 }]
338}
339
340#[cfg(test)]
341#[path = "../../unit_tests/models/graph/minimum_edge_cost_flow.rs"]
342mod tests;