problemreductions/models/graph/
integral_flow_with_multipliers.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry};
8use crate::topology::DirectedGraph;
9use crate::traits::Problem;
10use serde::{Deserialize, Serialize};
11
12inventory::submit! {
13 ProblemSchemaEntry {
14 name: "IntegralFlowWithMultipliers",
15 display_name: "Integral Flow With Multipliers",
16 aliases: &[],
17 dimensions: &[],
18 category: crate::registry::ProblemCategory::Graph,
19 module_path: module_path!(),
20 description: "Integral flow feasibility on a directed graph with multiplier-scaled conservation at non-terminal vertices",
21 fields: IntegralFlowWithMultipliersCreateSpec::FIELDS,
22 }
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct IntegralFlowWithMultipliers {
27 graph: DirectedGraph,
28 source: usize,
29 sink: usize,
30 multipliers: Vec<i64>,
31 capacities: Vec<i64>,
32 requirement: i64,
33}
34
35#[derive(Debug, Deserialize, crate::CreateSpec)]
36struct IntegralFlowWithMultipliersCreateSpec {
37 #[create(codec = "arc-list")]
38 arcs: Vec<(usize, usize)>,
39 num_vertices: Option<usize>,
40 #[create(codec = "comma-separated")]
41 capacities: Vec<i64>,
42 source: usize,
43 sink: usize,
44 #[create(codec = "comma-separated")]
45 multipliers: Vec<i64>,
46 requirement: i64,
47}
48
49impl TryFrom<IntegralFlowWithMultipliersCreateSpec> for IntegralFlowWithMultipliers {
50 type Error = crate::registry::ConstructionError;
51 fn try_from(
52 spec: IntegralFlowWithMultipliersCreateSpec,
53 ) -> Result<Self, crate::registry::ConstructionError> {
54 if spec.arcs.is_empty() {
55 return Err("arcs must be non-empty".into());
56 }
57 let inferred = spec
58 .arcs
59 .iter()
60 .flat_map(|&(u, v)| [u, v])
61 .max()
62 .map(|v| v.checked_add(1).ok_or("vertex count overflows usize"))
63 .transpose()?
64 .unwrap_or(0);
65 let count = spec.num_vertices.unwrap_or(inferred);
66 if count < inferred {
67 return Err("num_vertices is too small".into());
68 }
69 if spec.capacities.len() != spec.arcs.len() {
70 return Err("capacities length must match arcs length".into());
71 }
72 if spec.multipliers.len() != count {
73 return Err("multipliers length must match num_vertices".into());
74 }
75 if spec.source >= count || spec.sink >= count {
76 return Err("source and sink must be valid vertices".into());
77 }
78 if spec.source == spec.sink {
79 return Err("source and sink must be distinct".into());
80 }
81 for (v, &m) in spec.multipliers.iter().enumerate() {
82 if v != spec.source && v != spec.sink && m == 0 {
83 return Err("non-terminal multipliers must be positive".into());
84 }
85 }
86 for &c in &spec.capacities {
87 if usize::try_from(c)
88 .ok()
89 .and_then(|v| v.checked_add(1))
90 .is_none()
91 {
92 return Err("capacity is too large".into());
93 }
94 }
95 Ok(Self {
96 graph: DirectedGraph::new(count, spec.arcs),
97 source: spec.source,
98 sink: spec.sink,
99 multipliers: spec.multipliers,
100 capacities: spec.capacities,
101 requirement: spec.requirement,
102 })
103 }
104}
105
106impl IntegralFlowWithMultipliers {
107 pub fn new(
108 graph: DirectedGraph,
109 source: usize,
110 sink: usize,
111 multipliers: Vec<i64>,
112 capacities: Vec<i64>,
113 requirement: i64,
114 ) -> Self {
115 assert_eq!(
116 capacities.len(),
117 graph.num_arcs(),
118 "capacities length must match graph num_arcs"
119 );
120 assert_eq!(
121 multipliers.len(),
122 graph.num_vertices(),
123 "multipliers length must match graph num_vertices"
124 );
125
126 let num_vertices = graph.num_vertices();
127 assert!(
128 source < num_vertices,
129 "source ({source}) must be less than num_vertices ({num_vertices})"
130 );
131 assert!(
132 sink < num_vertices,
133 "sink ({sink}) must be less than num_vertices ({num_vertices})"
134 );
135 assert_ne!(source, sink, "source and sink must be distinct");
136
137 for (vertex, &multiplier) in multipliers.iter().enumerate() {
138 if vertex != source && vertex != sink {
139 assert!(multiplier > 0, "non-terminal multipliers must be positive");
140 }
141 }
142
143 for &capacity in &capacities {
144 let domain = usize::try_from(capacity)
145 .ok()
146 .and_then(|value| value.checked_add(1));
147 assert!(
148 domain.is_some(),
149 "arc capacities must fit into usize for dims()"
150 );
151 }
152
153 Self {
154 graph,
155 source,
156 sink,
157 multipliers,
158 capacities,
159 requirement,
160 }
161 }
162
163 pub fn graph(&self) -> &DirectedGraph {
164 &self.graph
165 }
166
167 pub fn source(&self) -> usize {
168 self.source
169 }
170
171 pub fn sink(&self) -> usize {
172 self.sink
173 }
174
175 pub fn multipliers(&self) -> &[i64] {
176 &self.multipliers
177 }
178
179 pub fn capacities(&self) -> &[i64] {
180 &self.capacities
181 }
182
183 pub fn requirement(&self) -> i64 {
184 self.requirement
185 }
186
187 pub fn num_vertices(&self) -> usize {
188 self.graph.num_vertices()
189 }
190
191 pub fn num_arcs(&self) -> usize {
192 self.graph.num_arcs()
193 }
194
195 pub fn max_capacity(&self) -> i64 {
196 self.capacities.iter().copied().max().unwrap_or(0)
197 }
198
199 fn domain_size(capacity: i64) -> usize {
200 usize::try_from(capacity)
201 .ok()
202 .and_then(|value| value.checked_add(1))
203 .expect("capacity already validated to fit into usize")
204 }
205
206 pub fn is_feasible(&self, config: &[usize]) -> Result<bool, crate::traits::EvaluationError> {
207 if config.len() != self.num_arcs() {
208 return Ok(false);
209 }
210
211 let num_vertices = self.num_vertices();
212 let mut inflow = vec![0_i64; num_vertices];
213 let mut outflow = vec![0_i64; num_vertices];
214
215 for (arc_index, ((u, v), &capacity)) in self
216 .graph
217 .arcs()
218 .into_iter()
219 .zip(self.capacities.iter())
220 .enumerate()
221 {
222 let Some(flow_usize) = config.get(arc_index).copied() else {
223 return Ok(false);
224 };
225 let Ok(flow_u64) = i64::try_from(flow_usize) else {
226 return Ok(false);
227 };
228 if flow_u64 > capacity {
229 return Ok(false);
230 }
231 outflow[u] = outflow[u].checked_add(flow_u64).ok_or_else(|| {
232 crate::traits::EvaluationError::IntegerOverflow(
233 "summing outgoing multiplied flow".into(),
234 )
235 })?;
236 inflow[v] = inflow[v].checked_add(flow_u64).ok_or_else(|| {
237 crate::traits::EvaluationError::IntegerOverflow(
238 "summing incoming multiplied flow".into(),
239 )
240 })?;
241 }
242
243 for vertex in 0..num_vertices {
244 if vertex == self.source || vertex == self.sink {
245 continue;
246 }
247 let expected_outflow = inflow[vertex]
248 .checked_mul(self.multipliers[vertex])
249 .ok_or_else(|| {
250 crate::traits::EvaluationError::IntegerOverflow(
251 "multiplying incoming flow by vertex multiplier".into(),
252 )
253 })?;
254 if expected_outflow != outflow[vertex] {
255 return Ok(false);
256 }
257 }
258
259 let sink_net_flow = inflow[self.sink]
260 .checked_sub(outflow[self.sink])
261 .ok_or_else(|| {
262 crate::traits::EvaluationError::IntegerOverflow(
263 "computing net flow into sink".into(),
264 )
265 })?;
266 Ok(sink_net_flow >= self.requirement)
267 }
268}
269
270impl Problem for IntegralFlowWithMultipliers {
271 const NAME: &'static str = "IntegralFlowWithMultipliers";
272 type Solution = Vec<usize>;
273 type Value = crate::types::Or;
274
275 crate::problem_parameters![
276 ("max_capacity", max_capacity),
277 ("num_arcs", num_arcs),
278 ("num_vertices", num_vertices),
279 ("requirement", requirement),
280 ];
281
282 fn evaluate(
283 &self,
284 config: &Self::Solution,
285 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
286 if config.len() != self.num_arcs() {
287 return Err(crate::traits::EvaluationError::InvalidConfiguration(
288 "flow vector length does not match the graph arcs".into(),
289 ));
290 }
291 Ok(crate::types::Or(self.is_feasible(config)?))
292 }
293
294 fn variant() -> Vec<(&'static str, &'static str)> {
295 crate::variant_params![]
296 }
297}
298
299impl crate::solvers::BruteForceProblem for IntegralFlowWithMultipliers {
300 fn dimensions(&self) -> Vec<usize> {
301 self.capacities
302 .iter()
303 .map(|&capacity| Self::domain_size(capacity))
304 .collect()
305 }
306}
307
308crate::declare_variants! {
309 default IntegralFlowWithMultipliers => "(max_capacity + 1)^num_arcs" create IntegralFlowWithMultipliersCreateSpec,
310}
311
312crate::register_brute_force! {
313 IntegralFlowWithMultipliers,
314}
315
316#[cfg(feature = "example-db")]
317pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
318 vec![crate::example_db::specs::ModelExampleSpec {
319 id: "integral_flow_with_multipliers",
320 instance: Box::new(IntegralFlowWithMultipliers::new(
321 DirectedGraph::new(
322 8,
323 vec![
324 (0, 1),
325 (0, 2),
326 (0, 3),
327 (0, 4),
328 (0, 5),
329 (0, 6),
330 (1, 7),
331 (2, 7),
332 (3, 7),
333 (4, 7),
334 (5, 7),
335 (6, 7),
336 ],
337 ),
338 0,
339 7,
340 vec![1, 2, 3, 4, 5, 6, 4, 1],
341 vec![1, 1, 1, 1, 1, 1, 2, 3, 4, 5, 6, 4],
342 12,
343 )),
344 optimal_config: serde_json::json!(vec![1, 0, 1, 0, 1, 0, 2, 0, 4, 0, 6, 0]),
345 optimal_value: serde_json::json!(true),
346 }]
347}
348
349#[cfg(test)]
350#[path = "../../unit_tests/models/graph/integral_flow_with_multipliers.rs"]
351mod tests;