Skip to main content

problemreductions/models/graph/
disjoint_connecting_paths.rs

1//! Disjoint Connecting Paths problem implementation.
2//!
3//! The problem asks whether an undirected graph contains pairwise
4//! vertex-disjoint paths connecting a prescribed collection of terminal pairs.
5
6use 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/// Disjoint Connecting Paths on an undirected graph.
29///
30/// A configuration uses one binary variable per edge in the graph's canonical
31/// sorted edge list. A valid solution selects exactly the edges of one simple
32/// path for each terminal pair, with all such paths pairwise vertex-disjoint.
33#[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    /// Create a new Disjoint Connecting Paths instance.
98    ///
99    /// # Panics
100    ///
101    /// Panics if no terminal pairs are provided, if a pair uses invalid or
102    /// repeated endpoints, or if any terminal appears in more than one pair.
103    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    /// Get a reference to the underlying graph.
134    pub fn graph(&self) -> &G {
135        &self.graph
136    }
137
138    /// Get the terminal pairs.
139    pub fn terminal_pairs(&self) -> &[(usize, usize)] {
140        &self.terminal_pairs
141    }
142
143    /// Get the number of vertices in the graph.
144    pub fn num_vertices(&self) -> usize {
145        self.graph.num_vertices()
146    }
147
148    /// Get the number of edges in the graph.
149    pub fn num_edges(&self) -> usize {
150        self.graph.num_edges()
151    }
152
153    /// Get the number of terminal pairs.
154    pub fn num_pairs(&self) -> usize {
155        self.terminal_pairs.len()
156    }
157
158    /// Return the canonical lexicographically sorted undirected edge list.
159    pub fn ordered_edges(&self) -> Vec<(usize, usize)> {
160        canonical_edges(&self.graph)
161    }
162
163    /// Check whether a configuration is a valid solution.
164    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;