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(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    /// Create a new Disjoint Connecting Paths instance.
97    ///
98    /// # Panics
99    ///
100    /// Panics if no terminal pairs are provided, if a pair uses invalid or
101    /// repeated endpoints, or if any terminal appears in more than one pair.
102    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    /// Get a reference to the underlying graph.
143    pub fn graph(&self) -> &G {
144        &self.graph
145    }
146
147    /// Get the terminal pairs.
148    pub fn terminal_pairs(&self) -> &[(usize, usize)] {
149        &self.terminal_pairs
150    }
151
152    /// Get the number of vertices in the graph.
153    pub fn num_vertices(&self) -> usize {
154        self.graph.num_vertices()
155    }
156
157    /// Get the number of edges in the graph.
158    pub fn num_edges(&self) -> usize {
159        self.graph.num_edges()
160    }
161
162    /// Get the number of terminal pairs.
163    pub fn num_pairs(&self) -> usize {
164        self.terminal_pairs.len()
165    }
166
167    /// Return the canonical lexicographically sorted undirected edge list.
168    pub fn ordered_edges(&self) -> Vec<(usize, usize)> {
169        canonical_edges(&self.graph)
170    }
171
172    /// Check whether a configuration is a valid solution.
173    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;