problemreductions/models/graph/
disjoint_connecting_paths.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry, VariantDimension};
7use crate::topology::{Graph, SimpleGraph};
8use crate::traits::Problem;
9use crate::variant::VariantParam;
10use serde::{Deserialize, Serialize};
11use std::collections::BTreeSet;
12
13inventory::submit! {
14 ProblemSchemaEntry {
15 name: "DisjointConnectingPaths",
16 display_name: "Disjoint Connecting Paths",
17 aliases: &[],
18 dimensions: &[
19 VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
20 ],
21 category: crate::registry::ProblemCategory::Graph,
22 module_path: module_path!(),
23 description: "Find pairwise vertex-disjoint paths connecting given terminal pairs",
24 fields: DisjointConnectingPathsCreateSpec::FIELDS,
25 }
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(try_from = "DisjointConnectingPathsData<G>")]
35#[serde(bound(deserialize = "G: Graph + Deserialize<'de>"))]
36pub struct DisjointConnectingPaths<G> {
37 graph: G,
38 terminal_pairs: Vec<(usize, usize)>,
39}
40
41#[derive(Deserialize)]
42#[serde(bound(deserialize = "G: Graph + Deserialize<'de>"))]
43struct DisjointConnectingPathsData<G> {
44 graph: G,
45 terminal_pairs: Vec<(usize, usize)>,
46}
47
48impl<G> TryFrom<DisjointConnectingPathsData<G>> for DisjointConnectingPaths<G>
49where
50 G: Graph,
51{
52 type Error = crate::registry::ConstructionError;
53
54 fn try_from(data: DisjointConnectingPathsData<G>) -> Result<Self, Self::Error> {
55 Self::try_new(data.graph, data.terminal_pairs)
56 }
57}
58
59#[derive(Debug, Deserialize, crate::CreateSpec)]
60struct DisjointConnectingPathsCreateSpec {
61 #[create(codec = "edge-list")]
62 graph: Vec<(usize, usize)>,
63 num_vertices: Option<usize>,
64 #[create(codec = "edge-list")]
65 terminal_pairs: Vec<(usize, usize)>,
66}
67
68impl TryFrom<DisjointConnectingPathsCreateSpec> for DisjointConnectingPaths<SimpleGraph> {
69 type Error = crate::registry::ConstructionError;
70 fn try_from(spec: DisjointConnectingPathsCreateSpec) -> Result<Self, Self::Error> {
71 if spec.graph.is_empty() && spec.num_vertices.is_none() {
72 return Err("num_vertices is required for an empty graph".into());
73 }
74 for &(u, v) in &spec.graph {
75 if u == v {
76 return Err(format!("self-loop {u}-{v} is not allowed").into());
77 }
78 }
79 let inferred = spec
80 .graph
81 .iter()
82 .flat_map(|&(u, v)| [u, v])
83 .max()
84 .map(|v| v.checked_add(1).ok_or("vertex count overflows usize"))
85 .transpose()?
86 .unwrap_or(0);
87 let count = spec.num_vertices.unwrap_or(inferred);
88 if count < inferred {
89 return Err("num_vertices is too small for graph endpoints".into());
90 }
91 Self::try_new(SimpleGraph::new(count, spec.graph), spec.terminal_pairs)
92 }
93}
94
95impl<G: Graph> DisjointConnectingPaths<G> {
96 pub fn new(graph: G, terminal_pairs: Vec<(usize, usize)>) -> Self {
103 Self::try_new(graph, terminal_pairs).unwrap_or_else(|error| panic!("{error}"))
104 }
105
106 fn try_new(
107 graph: G,
108 terminal_pairs: Vec<(usize, usize)>,
109 ) -> Result<Self, crate::registry::ConstructionError> {
110 if terminal_pairs.is_empty() {
111 return Err("terminal_pairs must contain at least one pair".into());
112 }
113
114 let num_vertices = graph.num_vertices();
115 let mut used = vec![false; num_vertices];
116 for &(source, sink) in &terminal_pairs {
117 if source >= num_vertices {
118 return Err("terminal pair source out of bounds".into());
119 }
120 if sink >= num_vertices {
121 return Err("terminal pair sink out of bounds".into());
122 }
123 if source == sink {
124 return Err("terminal pair endpoints must be distinct".into());
125 }
126 if used[source] {
127 return Err("terminal vertices must be pairwise disjoint across pairs".into());
128 }
129 if used[sink] {
130 return Err("terminal vertices must be pairwise disjoint across pairs".into());
131 }
132 used[source] = true;
133 used[sink] = true;
134 }
135
136 Ok(Self {
137 graph,
138 terminal_pairs,
139 })
140 }
141
142 pub fn graph(&self) -> &G {
144 &self.graph
145 }
146
147 pub fn terminal_pairs(&self) -> &[(usize, usize)] {
149 &self.terminal_pairs
150 }
151
152 pub fn num_vertices(&self) -> usize {
154 self.graph.num_vertices()
155 }
156
157 pub fn num_edges(&self) -> usize {
159 self.graph.num_edges()
160 }
161
162 pub fn num_pairs(&self) -> usize {
164 self.terminal_pairs.len()
165 }
166
167 pub fn ordered_edges(&self) -> Vec<(usize, usize)> {
169 canonical_edges(&self.graph)
170 }
171
172 pub fn is_valid_solution(&self, config: &[bool]) -> bool {
174 is_valid_disjoint_connecting_paths(&self.graph, &self.terminal_pairs, config)
175 }
176}
177
178impl<G> Problem for DisjointConnectingPaths<G>
179where
180 G: Graph + VariantParam,
181{
182 const NAME: &'static str = "DisjointConnectingPaths";
183 type Solution = Vec<bool>;
184 type Value = crate::types::Or;
185
186 crate::problem_parameters![
187 ("num_edges", num_edges),
188 ("num_pairs", num_pairs),
189 ("num_vertices", num_vertices),
190 ];
191
192 fn variant() -> Vec<(&'static str, &'static str)> {
193 crate::variant_params![G]
194 }
195
196 fn evaluate(
197 &self,
198 config: &Self::Solution,
199 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
200 if config.len() != self.num_edges() {
201 return Err(crate::traits::EvaluationError::InvalidConfiguration(
202 "edge-selection length does not match the graph".into(),
203 ));
204 }
205 Ok(crate::types::Or(self.is_valid_solution(config)))
206 }
207}
208
209impl<G> crate::solvers::BruteForceProblem for DisjointConnectingPaths<G>
210where
211 G: Graph + VariantParam,
212{
213 fn dimensions(&self) -> Vec<usize> {
214 vec![2; self.num_edges()]
215 }
216}
217
218fn canonical_edges<G: Graph>(graph: &G) -> Vec<(usize, usize)> {
219 let mut edges = graph
220 .edges()
221 .into_iter()
222 .map(|(u, v)| if u <= v { (u, v) } else { (v, u) })
223 .collect::<Vec<_>>();
224 edges.sort_unstable();
225 edges
226}
227
228fn normalize_edge(u: usize, v: usize) -> (usize, usize) {
229 if u <= v {
230 (u, v)
231 } else {
232 (v, u)
233 }
234}
235
236fn is_valid_disjoint_connecting_paths<G: Graph>(
237 graph: &G,
238 terminal_pairs: &[(usize, usize)],
239 config: &[bool],
240) -> bool {
241 let edges = canonical_edges(graph);
242 if config.len() != edges.len() {
243 return false;
244 }
245 let num_vertices = graph.num_vertices();
246 let mut adjacency = vec![Vec::new(); num_vertices];
247 let mut degrees = vec![0usize; num_vertices];
248 for (index, &chosen) in config.iter().enumerate() {
249 if chosen {
250 let (u, v) = edges[index];
251 adjacency[u].push(v);
252 adjacency[v].push(u);
253 degrees[u] += 1;
254 degrees[v] += 1;
255 }
256 }
257
258 let mut terminal_vertices = vec![false; num_vertices];
259 let required_pairs = terminal_pairs
260 .iter()
261 .map(|&(u, v)| {
262 terminal_vertices[u] = true;
263 terminal_vertices[v] = true;
264 normalize_edge(u, v)
265 })
266 .collect::<BTreeSet<_>>();
267 let mut matched_pairs = BTreeSet::new();
268 let mut visited = vec![false; num_vertices];
269 let mut component_count = 0usize;
270
271 for start in 0..num_vertices {
272 if degrees[start] == 0 || visited[start] {
273 continue;
274 }
275
276 component_count += 1;
277 let mut stack = vec![start];
278 let mut vertices = Vec::new();
279 let mut degree_sum = 0usize;
280 visited[start] = true;
281
282 while let Some(vertex) = stack.pop() {
283 vertices.push(vertex);
284 degree_sum += degrees[vertex];
285 for &neighbor in &adjacency[vertex] {
286 if !visited[neighbor] {
287 visited[neighbor] = true;
288 stack.push(neighbor);
289 }
290 }
291 }
292
293 let edge_count = degree_sum / 2;
294 if edge_count + 1 != vertices.len() {
295 return false;
296 }
297
298 let mut endpoints = Vec::new();
299 for &vertex in &vertices {
300 match degrees[vertex] {
301 1 => endpoints.push(vertex),
302 2 => {
303 if terminal_vertices[vertex] {
304 return false;
305 }
306 }
307 _ => return false,
308 }
309 }
310
311 if endpoints.len() != 2 {
312 return false;
313 }
314
315 let realized_pair = normalize_edge(endpoints[0], endpoints[1]);
316 if !required_pairs.contains(&realized_pair) || !matched_pairs.insert(realized_pair) {
317 return false;
318 }
319 }
320
321 component_count == terminal_pairs.len() && matched_pairs.len() == terminal_pairs.len()
322}
323
324crate::declare_variants! {
325 default DisjointConnectingPaths<SimpleGraph> => "2^num_edges" create DisjointConnectingPathsCreateSpec,
326}
327
328crate::register_brute_force! {
329 DisjointConnectingPaths<SimpleGraph> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
330}
331
332#[cfg(feature = "example-db")]
333pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
334 vec![crate::example_db::specs::ModelExampleSpec {
335 id: "disjoint_connecting_paths_simplegraph",
336 instance: Box::new(DisjointConnectingPaths::new(
337 SimpleGraph::new(
338 6,
339 vec![(0, 1), (1, 3), (0, 2), (1, 4), (2, 4), (3, 5), (4, 5)],
340 ),
341 vec![(0, 3), (2, 5)],
342 )),
343 optimal_config: serde_json::json!(vec![true, false, true, false, true, false, true]),
344 optimal_value: serde_json::json!(true),
345 }]
346}
347
348#[cfg(test)]
349#[path = "../../unit_tests/models/graph/disjoint_connecting_paths.rs"]
350mod tests;