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)]
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;