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