Skip to main content

problemreductions/rules/
hamiltoniancircuit_ruralpostman.rs

1//! Reduction from HamiltonianCircuit to RuralPostman.
2//!
3//! Vertex-splitting construction inspired by Lenstra & Rinnooy Kan (1976).
4//!
5//! # Construction
6//!
7//! Given a graph G = (V, E) with n vertices and m edges:
8//! - Split each vertex v_i into v_i^a (vertex 2i) and v_i^b (vertex 2i+1).
9//! - Add a required edge {v_i^a, v_i^b} with weight 1 for each vertex (n required edges).
10//! - For each edge {v_i, v_j} in E, add two non-required connectivity edges:
11//!   {v_i^b, v_j^a} and {v_j^b, v_i^a}, each with weight 1.
12//!
13//! The target graph has 2n vertices, n + 2m edges, and n required edges.
14//!
15//! # Correctness
16//!
17//! G has a Hamiltonian circuit iff the optimal RPP cost equals 2n:
18//! - If G has HC (v_{p_0}, ..., v_{p_{n-1}}): the RPP tour traverses
19//!   v_{p_0}^a -> v_{p_0}^b -> v_{p_1}^a -> v_{p_1}^b -> ... -> v_{p_0}^a,
20//!   using n required edges (cost n) and n connectivity edges (cost n), total 2n.
21//! - If G has no HC: every valid RPP tour covering all required edges needs
22//!   strictly more than n connectivity edges (the bipartite graph between
23//!   b-vertices and a-vertices does not admit a perfect matching corresponding
24//!   to a Hamiltonian circuit), so cost > 2n.
25
26use crate::models::graph::{HamiltonianCircuit, RuralPostman};
27use crate::reduction;
28use crate::rules::traits::{ReduceTo, ReductionResult};
29use crate::topology::{Graph, SimpleGraph};
30
31/// Result of reducing HamiltonianCircuit to RuralPostman.
32#[derive(Debug, Clone)]
33pub struct ReductionHamiltonianCircuitToRuralPostman {
34    target: RuralPostman<SimpleGraph, i64>,
35    /// Number of vertices in the original graph.
36    n: usize,
37    /// Edges of the original graph (for solution extraction).
38    source_edges: Vec<(usize, usize)>,
39}
40
41impl ReductionResult for ReductionHamiltonianCircuitToRuralPostman {
42    type Source = HamiltonianCircuit<SimpleGraph>;
43    type Target = RuralPostman<SimpleGraph, i64>;
44
45    fn target_problem(&self) -> &Self::Target {
46        &self.target
47    }
48
49    fn extract_solution(
50        &self,
51        target_solution: &<Self::Target as crate::traits::Problem>::Solution,
52    ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
53        crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
54
55        Ok({
56            // The target solution is edge multiplicities.
57            // Required edges are indices 0..n (the {v_i^a, v_i^b} edges).
58            // Connectivity edges start at index n.
59            // For each source edge (v_i, v_j) at source index k:
60            //   target edge n + 2*k is {v_i^b, v_j^a}
61            //   target edge n + 2*k + 1 is {v_j^b, v_i^a}
62            //
63            // A connectivity edge {v_i^b, v_j^a} used with multiplicity 1 means
64            // the tour goes from vertex i to vertex j (j follows i in the HC).
65
66            let n = self.n;
67
68            // Build successor map from connectivity edges used exactly once
69            let mut successor = vec![usize::MAX; n];
70            for (k, &(vi, vj)) in self.source_edges.iter().enumerate() {
71                let fwd_idx = n + 2 * k; // {v_i^b, v_j^a}
72                let bwd_idx = n + 2 * k + 1; // {v_j^b, v_i^a}
73
74                let fwd_mult = target_solution[fwd_idx];
75                let bwd_mult = target_solution[bwd_idx];
76
77                // In an optimal HC solution, each connectivity edge is used 0 or 1 times.
78                // Each vertex should have exactly one outgoing connectivity edge.
79                if fwd_mult > 0 && successor[vi] == usize::MAX {
80                    successor[vi] = vj;
81                }
82                if bwd_mult > 0 && successor[vj] == usize::MAX {
83                    successor[vj] = vi;
84                }
85            }
86
87            // Walk the successor chain starting from vertex 0
88            let mut cycle = Vec::with_capacity(n);
89            let mut current = 0;
90            for _ in 0..n {
91                cycle.push(current);
92                let next = successor[current];
93                if next == usize::MAX {
94                    return Err(crate::rules::ExtractionError::invalid(
95                        "target tour does not provide one successor for every source vertex",
96                    ));
97                }
98                current = next;
99            }
100
101            cycle
102        })
103    }
104}
105
106#[reduction(
107    transform = exact {
108        num_vertices = "2 * num_vertices",
109        num_edges = "num_vertices + 2 * num_edges",
110        num_required_edges = "num_vertices",
111    }
112)]
113impl ReduceTo<RuralPostman<SimpleGraph, i64>> for HamiltonianCircuit<SimpleGraph> {
114    type Result = ReductionHamiltonianCircuitToRuralPostman;
115
116    fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
117        let n = self.num_vertices();
118        let source_edges: Vec<(usize, usize)> = self.graph().edges();
119        let m = source_edges.len();
120
121        // Build target graph with 2n vertices
122        let num_target_edges = n + 2 * m;
123        let mut target_edges = Vec::with_capacity(num_target_edges);
124        let mut edge_weights = Vec::with_capacity(num_target_edges);
125        let mut required_edges = Vec::with_capacity(n);
126
127        // Required edges: {v_i^a, v_i^b} = {2i, 2i+1} with weight 1
128        for i in 0..n {
129            target_edges.push((2 * i, 2 * i + 1));
130            edge_weights.push(1);
131            required_edges.push(i); // edge index i is required
132        }
133
134        // Connectivity edges for each source edge {v_i, v_j}:
135        //   {v_i^b, v_j^a} = {2i+1, 2j} with weight 1
136        //   {v_j^b, v_i^a} = {2j+1, 2i} with weight 1
137        for &(vi, vj) in &source_edges {
138            target_edges.push((2 * vi + 1, 2 * vj));
139            edge_weights.push(1);
140            target_edges.push((2 * vj + 1, 2 * vi));
141            edge_weights.push(1);
142        }
143
144        let target_graph = SimpleGraph::new(2 * n, target_edges);
145        let target = RuralPostman::new(target_graph, edge_weights, required_edges);
146
147        Ok(ReductionHamiltonianCircuitToRuralPostman {
148            target,
149            n,
150            source_edges,
151        })
152    }
153}
154
155#[cfg(feature = "example-db")]
156pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
157    use crate::export::SolutionPair;
158
159    vec![crate::example_db::specs::RuleExampleSpec {
160        id: "hamiltoniancircuit_to_ruralpostman",
161        build: || {
162            // Triangle graph: 3 vertices, 3 edges, HC = [0, 1, 2]
163            let source = HamiltonianCircuit::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]));
164
165            // Target graph has 6 vertices, 3 + 6 = 9 edges, 3 required edges.
166            // HC [0, 1, 2] uses connectivity edges:
167            //   0->1: fwd edge of source edge 0=(0,1), idx=3
168            //   1->2: fwd edge of source edge 1=(1,2), idx=5
169            //   2->0: bwd edge of source edge 2=(0,2), idx=8
170            // Required edges all have multiplicity 1.
171            // target_config = [1, 1, 1, 1, 0, 1, 0, 0, 1]
172            crate::example_db::specs::rule_example_with_witness::<_, RuralPostman<SimpleGraph, i64>>(
173                source,
174                SolutionPair {
175                    source_config: serde_json::json!(vec![0, 1, 2]),
176                    target_config: serde_json::json!(vec![1, 1, 1, 1, 0, 1, 0, 0, 1]),
177                },
178            )
179        },
180    }]
181}
182
183#[cfg(test)]
184#[path = "../unit_tests/rules/hamiltoniancircuit_ruralpostman.rs"]
185mod tests;