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