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::{FieldInfo, 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        module_path: module_path!(),
22        description: "Find pairwise vertex-disjoint paths connecting given terminal pairs",
23        fields: &[
24            FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E)" },
25            FieldInfo { name: "terminal_pairs", type_name: "Vec<(usize, usize)>", description: "Disjoint terminal pairs (s_i, t_i)" },
26        ],
27    }
28}
29
30/// Disjoint Connecting Paths on an undirected graph.
31///
32/// A configuration uses one binary variable per edge in the graph's canonical
33/// sorted edge list. A valid solution selects exactly the edges of one simple
34/// path for each terminal pair, with all such paths pairwise vertex-disjoint.
35#[derive(Debug, Clone, Serialize, Deserialize)]
36#[serde(bound(deserialize = "G: serde::Deserialize<'de>"))]
37pub struct DisjointConnectingPaths<G> {
38    graph: G,
39    terminal_pairs: Vec<(usize, usize)>,
40}
41
42impl<G: Graph> DisjointConnectingPaths<G> {
43    /// Create a new Disjoint Connecting Paths instance.
44    ///
45    /// # Panics
46    ///
47    /// Panics if no terminal pairs are provided, if a pair uses invalid or
48    /// repeated endpoints, or if any terminal appears in more than one pair.
49    pub fn new(graph: G, terminal_pairs: Vec<(usize, usize)>) -> Self {
50        assert!(
51            !terminal_pairs.is_empty(),
52            "terminal_pairs must contain at least one pair"
53        );
54
55        let num_vertices = graph.num_vertices();
56        let mut used = vec![false; num_vertices];
57        for &(source, sink) in &terminal_pairs {
58            assert!(source < num_vertices, "terminal pair source out of bounds");
59            assert!(sink < num_vertices, "terminal pair sink out of bounds");
60            assert_ne!(source, sink, "terminal pair endpoints must be distinct");
61            assert!(
62                !used[source],
63                "terminal vertices must be pairwise disjoint across pairs"
64            );
65            assert!(
66                !used[sink],
67                "terminal vertices must be pairwise disjoint across pairs"
68            );
69            used[source] = true;
70            used[sink] = true;
71        }
72
73        Self {
74            graph,
75            terminal_pairs,
76        }
77    }
78
79    /// Get a reference to the underlying graph.
80    pub fn graph(&self) -> &G {
81        &self.graph
82    }
83
84    /// Get the terminal pairs.
85    pub fn terminal_pairs(&self) -> &[(usize, usize)] {
86        &self.terminal_pairs
87    }
88
89    /// Get the number of vertices in the graph.
90    pub fn num_vertices(&self) -> usize {
91        self.graph.num_vertices()
92    }
93
94    /// Get the number of edges in the graph.
95    pub fn num_edges(&self) -> usize {
96        self.graph.num_edges()
97    }
98
99    /// Get the number of terminal pairs.
100    pub fn num_pairs(&self) -> usize {
101        self.terminal_pairs.len()
102    }
103
104    /// Return the canonical lexicographically sorted undirected edge list.
105    pub fn ordered_edges(&self) -> Vec<(usize, usize)> {
106        canonical_edges(&self.graph)
107    }
108
109    /// Check whether a configuration is a valid solution.
110    pub fn is_valid_solution(&self, config: &[usize]) -> bool {
111        is_valid_disjoint_connecting_paths(&self.graph, &self.terminal_pairs, config)
112    }
113}
114
115impl<G> Problem for DisjointConnectingPaths<G>
116where
117    G: Graph + VariantParam,
118{
119    const NAME: &'static str = "DisjointConnectingPaths";
120    type Value = crate::types::Or;
121
122    fn variant() -> Vec<(&'static str, &'static str)> {
123        crate::variant_params![G]
124    }
125
126    fn dims(&self) -> Vec<usize> {
127        vec![2; self.num_edges()]
128    }
129
130    fn evaluate(&self, config: &[usize]) -> crate::types::Or {
131        crate::types::Or(self.is_valid_solution(config))
132    }
133}
134
135fn canonical_edges<G: Graph>(graph: &G) -> Vec<(usize, usize)> {
136    let mut edges = graph
137        .edges()
138        .into_iter()
139        .map(|(u, v)| if u <= v { (u, v) } else { (v, u) })
140        .collect::<Vec<_>>();
141    edges.sort_unstable();
142    edges
143}
144
145fn normalize_edge(u: usize, v: usize) -> (usize, usize) {
146    if u <= v {
147        (u, v)
148    } else {
149        (v, u)
150    }
151}
152
153fn is_valid_disjoint_connecting_paths<G: Graph>(
154    graph: &G,
155    terminal_pairs: &[(usize, usize)],
156    config: &[usize],
157) -> bool {
158    let edges = canonical_edges(graph);
159    if config.len() != edges.len() {
160        return false;
161    }
162    if config.iter().any(|&value| value > 1) {
163        return false;
164    }
165
166    let num_vertices = graph.num_vertices();
167    let mut adjacency = vec![Vec::new(); num_vertices];
168    let mut degrees = vec![0usize; num_vertices];
169    for (index, &chosen) in config.iter().enumerate() {
170        if chosen == 1 {
171            let (u, v) = edges[index];
172            adjacency[u].push(v);
173            adjacency[v].push(u);
174            degrees[u] += 1;
175            degrees[v] += 1;
176        }
177    }
178
179    let mut terminal_vertices = vec![false; num_vertices];
180    let required_pairs = terminal_pairs
181        .iter()
182        .map(|&(u, v)| {
183            terminal_vertices[u] = true;
184            terminal_vertices[v] = true;
185            normalize_edge(u, v)
186        })
187        .collect::<BTreeSet<_>>();
188    let mut matched_pairs = BTreeSet::new();
189    let mut visited = vec![false; num_vertices];
190    let mut component_count = 0usize;
191
192    for start in 0..num_vertices {
193        if degrees[start] == 0 || visited[start] {
194            continue;
195        }
196
197        component_count += 1;
198        let mut stack = vec![start];
199        let mut vertices = Vec::new();
200        let mut degree_sum = 0usize;
201        visited[start] = true;
202
203        while let Some(vertex) = stack.pop() {
204            vertices.push(vertex);
205            degree_sum += degrees[vertex];
206            for &neighbor in &adjacency[vertex] {
207                if !visited[neighbor] {
208                    visited[neighbor] = true;
209                    stack.push(neighbor);
210                }
211            }
212        }
213
214        let edge_count = degree_sum / 2;
215        if edge_count + 1 != vertices.len() {
216            return false;
217        }
218
219        let mut endpoints = Vec::new();
220        for &vertex in &vertices {
221            match degrees[vertex] {
222                1 => endpoints.push(vertex),
223                2 => {
224                    if terminal_vertices[vertex] {
225                        return false;
226                    }
227                }
228                _ => return false,
229            }
230        }
231
232        if endpoints.len() != 2 {
233            return false;
234        }
235
236        let realized_pair = normalize_edge(endpoints[0], endpoints[1]);
237        if !required_pairs.contains(&realized_pair) || !matched_pairs.insert(realized_pair) {
238            return false;
239        }
240    }
241
242    component_count == terminal_pairs.len() && matched_pairs.len() == terminal_pairs.len()
243}
244
245crate::declare_variants! {
246    default DisjointConnectingPaths<SimpleGraph> => "2^num_edges",
247}
248
249#[cfg(feature = "example-db")]
250pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
251    vec![crate::example_db::specs::ModelExampleSpec {
252        id: "disjoint_connecting_paths_simplegraph",
253        instance: Box::new(DisjointConnectingPaths::new(
254            SimpleGraph::new(
255                6,
256                vec![(0, 1), (1, 3), (0, 2), (1, 4), (2, 4), (3, 5), (4, 5)],
257            ),
258            vec![(0, 3), (2, 5)],
259        )),
260        optimal_config: vec![1, 0, 1, 0, 1, 0, 1],
261        optimal_value: serde_json::json!(true),
262    }]
263}
264
265#[cfg(test)]
266#[path = "../../unit_tests/models/graph/disjoint_connecting_paths.rs"]
267mod tests;