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