Skip to main content

problemreductions/models/graph/
integral_flow_with_multipliers.rs

1//! Integral Flow With Multipliers problem implementation.
2//!
3//! Given a directed graph with arc capacities, vertex multipliers on
4//! non-terminals, and a sink demand, determine whether there exists an
5//! integral flow satisfying multiplier-scaled conservation.
6
7use crate::registry::{FieldInfo, ProblemSchemaEntry, ProblemSizeFieldEntry};
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        module_path: module_path!(),
19        description: "Integral flow feasibility on a directed graph with multiplier-scaled conservation at non-terminal vertices",
20        fields: &[
21            FieldInfo { name: "graph", type_name: "DirectedGraph", description: "Directed graph G = (V, A)" },
22            FieldInfo { name: "source", type_name: "usize", description: "Source vertex s" },
23            FieldInfo { name: "sink", type_name: "usize", description: "Sink vertex t" },
24            FieldInfo { name: "multipliers", type_name: "Vec<u64>", description: "Vertex multipliers h(v) in vertex order; source/sink entries are ignored" },
25            FieldInfo { name: "capacities", type_name: "Vec<u64>", description: "Arc capacities c(a) in graph arc order" },
26            FieldInfo { name: "requirement", type_name: "u64", description: "Required net inflow R at the sink" },
27        ],
28    }
29}
30
31inventory::submit! {
32    ProblemSizeFieldEntry {
33        name: "IntegralFlowWithMultipliers",
34        fields: &["num_vertices", "num_arcs", "max_capacity", "requirement"],
35    }
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct IntegralFlowWithMultipliers {
40    graph: DirectedGraph,
41    source: usize,
42    sink: usize,
43    multipliers: Vec<u64>,
44    capacities: Vec<u64>,
45    requirement: u64,
46}
47
48impl IntegralFlowWithMultipliers {
49    pub fn new(
50        graph: DirectedGraph,
51        source: usize,
52        sink: usize,
53        multipliers: Vec<u64>,
54        capacities: Vec<u64>,
55        requirement: u64,
56    ) -> Self {
57        assert_eq!(
58            capacities.len(),
59            graph.num_arcs(),
60            "capacities length must match graph num_arcs"
61        );
62        assert_eq!(
63            multipliers.len(),
64            graph.num_vertices(),
65            "multipliers length must match graph num_vertices"
66        );
67
68        let num_vertices = graph.num_vertices();
69        assert!(
70            source < num_vertices,
71            "source ({source}) must be less than num_vertices ({num_vertices})"
72        );
73        assert!(
74            sink < num_vertices,
75            "sink ({sink}) must be less than num_vertices ({num_vertices})"
76        );
77        assert_ne!(source, sink, "source and sink must be distinct");
78
79        for (vertex, &multiplier) in multipliers.iter().enumerate() {
80            if vertex != source && vertex != sink {
81                assert!(multiplier > 0, "non-terminal multipliers must be positive");
82            }
83        }
84
85        for &capacity in &capacities {
86            let domain = usize::try_from(capacity)
87                .ok()
88                .and_then(|value| value.checked_add(1));
89            assert!(
90                domain.is_some(),
91                "arc capacities must fit into usize for dims()"
92            );
93        }
94
95        Self {
96            graph,
97            source,
98            sink,
99            multipliers,
100            capacities,
101            requirement,
102        }
103    }
104
105    pub fn graph(&self) -> &DirectedGraph {
106        &self.graph
107    }
108
109    pub fn source(&self) -> usize {
110        self.source
111    }
112
113    pub fn sink(&self) -> usize {
114        self.sink
115    }
116
117    pub fn multipliers(&self) -> &[u64] {
118        &self.multipliers
119    }
120
121    pub fn capacities(&self) -> &[u64] {
122        &self.capacities
123    }
124
125    pub fn requirement(&self) -> u64 {
126        self.requirement
127    }
128
129    pub fn num_vertices(&self) -> usize {
130        self.graph.num_vertices()
131    }
132
133    pub fn num_arcs(&self) -> usize {
134        self.graph.num_arcs()
135    }
136
137    pub fn max_capacity(&self) -> u64 {
138        self.capacities.iter().copied().max().unwrap_or(0)
139    }
140
141    fn domain_size(capacity: u64) -> usize {
142        usize::try_from(capacity)
143            .ok()
144            .and_then(|value| value.checked_add(1))
145            .expect("capacity already validated to fit into usize")
146    }
147
148    pub fn is_feasible(&self, config: &[usize]) -> bool {
149        if config.len() != self.num_arcs() {
150            return false;
151        }
152
153        let num_vertices = self.num_vertices();
154        let mut inflow = vec![0_i128; num_vertices];
155        let mut outflow = vec![0_i128; num_vertices];
156
157        for (arc_index, ((u, v), &capacity)) in self
158            .graph
159            .arcs()
160            .into_iter()
161            .zip(self.capacities.iter())
162            .enumerate()
163        {
164            let Some(flow_usize) = config.get(arc_index).copied() else {
165                return false;
166            };
167            let Ok(flow_u64) = u64::try_from(flow_usize) else {
168                return false;
169            };
170            if flow_u64 > capacity {
171                return false;
172            }
173            let flow = i128::from(flow_u64);
174            outflow[u] += flow;
175            inflow[v] += flow;
176        }
177
178        for vertex in 0..num_vertices {
179            if vertex == self.source || vertex == self.sink {
180                continue;
181            }
182            let multiplier = i128::from(self.multipliers[vertex]);
183            let Some(expected_outflow) = inflow[vertex].checked_mul(multiplier) else {
184                return false;
185            };
186            if expected_outflow != outflow[vertex] {
187                return false;
188            }
189        }
190
191        let sink_net_flow = inflow[self.sink] - outflow[self.sink];
192        sink_net_flow >= i128::from(self.requirement)
193    }
194}
195
196impl Problem for IntegralFlowWithMultipliers {
197    const NAME: &'static str = "IntegralFlowWithMultipliers";
198    type Value = crate::types::Or;
199
200    fn dims(&self) -> Vec<usize> {
201        self.capacities
202            .iter()
203            .map(|&capacity| Self::domain_size(capacity))
204            .collect()
205    }
206
207    fn evaluate(&self, config: &[usize]) -> crate::types::Or {
208        crate::types::Or(self.is_feasible(config))
209    }
210
211    fn variant() -> Vec<(&'static str, &'static str)> {
212        crate::variant_params![]
213    }
214}
215
216crate::declare_variants! {
217    default IntegralFlowWithMultipliers => "(max_capacity + 1)^num_arcs",
218}
219
220#[cfg(feature = "example-db")]
221pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
222    vec![crate::example_db::specs::ModelExampleSpec {
223        id: "integral_flow_with_multipliers",
224        instance: Box::new(IntegralFlowWithMultipliers::new(
225            DirectedGraph::new(
226                8,
227                vec![
228                    (0, 1),
229                    (0, 2),
230                    (0, 3),
231                    (0, 4),
232                    (0, 5),
233                    (0, 6),
234                    (1, 7),
235                    (2, 7),
236                    (3, 7),
237                    (4, 7),
238                    (5, 7),
239                    (6, 7),
240                ],
241            ),
242            0,
243            7,
244            vec![1, 2, 3, 4, 5, 6, 4, 1],
245            vec![1, 1, 1, 1, 1, 1, 2, 3, 4, 5, 6, 4],
246            12,
247        )),
248        optimal_config: vec![1, 0, 1, 0, 1, 0, 2, 0, 4, 0, 6, 0],
249        optimal_value: serde_json::json!(true),
250    }]
251}
252
253#[cfg(test)]
254#[path = "../../unit_tests/models/graph/integral_flow_with_multipliers.rs"]
255mod tests;