Skip to main content

problemreductions/models/graph/
integral_flow_homologous_arcs.rs

1//! Integral Flow with Homologous Arcs problem implementation.
2//!
3//! Given a directed capacitated network with a source, sink, and pairs of arcs
4//! that must carry equal flow, determine whether an integral flow meeting the
5//! required sink inflow exists.
6
7use 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/// Integral flow with homologous arcs.
26///
27/// A configuration stores one non-negative integer flow value for each arc in
28/// the graph's arc order. The assignment is feasible when it respects arc
29/// capacities, flow conservation at non-terminal vertices, every homologous-pair
30/// equality constraint, and the required net inflow at the sink.
31#[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;