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, i32>,
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, i32>;
44
45    fn target_problem(&self) -> &Self::Target {
46        &self.target
47    }
48
49    fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
50        // The target solution is edge multiplicities.
51        // Required edges are indices 0..n (the {v_i^a, v_i^b} edges).
52        // Connectivity edges start at index n.
53        // For each source edge (v_i, v_j) at source index k:
54        //   target edge n + 2*k is {v_i^b, v_j^a}
55        //   target edge n + 2*k + 1 is {v_j^b, v_i^a}
56        //
57        // A connectivity edge {v_i^b, v_j^a} used with multiplicity 1 means
58        // the tour goes from vertex i to vertex j (j follows i in the HC).
59
60        let n = self.n;
61
62        // Build successor map from connectivity edges used exactly once
63        let mut successor = vec![usize::MAX; n];
64        for (k, &(vi, vj)) in self.source_edges.iter().enumerate() {
65            let fwd_idx = n + 2 * k; // {v_i^b, v_j^a}
66            let bwd_idx = n + 2 * k + 1; // {v_j^b, v_i^a}
67
68            let fwd_mult = target_solution.get(fwd_idx).copied().unwrap_or(0);
69            let bwd_mult = target_solution.get(bwd_idx).copied().unwrap_or(0);
70
71            // In an optimal HC solution, each connectivity edge is used 0 or 1 times.
72            // Each vertex should have exactly one outgoing connectivity edge.
73            if fwd_mult > 0 && successor[vi] == usize::MAX {
74                successor[vi] = vj;
75            }
76            if bwd_mult > 0 && successor[vj] == usize::MAX {
77                successor[vj] = vi;
78            }
79        }
80
81        // Walk the successor chain starting from vertex 0
82        let mut cycle = Vec::with_capacity(n);
83        let mut current = 0;
84        for _ in 0..n {
85            cycle.push(current);
86            let next = successor[current];
87            if next == usize::MAX {
88                // No valid successor found; return fallback
89                return vec![0; n];
90            }
91            current = next;
92        }
93
94        cycle
95    }
96}
97
98#[reduction(
99    overhead = {
100        num_vertices = "2 * num_vertices",
101        num_edges = "num_vertices + 2 * num_edges",
102        num_required_edges = "num_vertices",
103    }
104)]
105impl ReduceTo<RuralPostman<SimpleGraph, i32>> for HamiltonianCircuit<SimpleGraph> {
106    type Result = ReductionHamiltonianCircuitToRuralPostman;
107
108    fn reduce_to(&self) -> Self::Result {
109        let n = self.num_vertices();
110        let source_edges: Vec<(usize, usize)> = self.graph().edges();
111        let m = source_edges.len();
112
113        // Build target graph with 2n vertices
114        let num_target_edges = n + 2 * m;
115        let mut target_edges = Vec::with_capacity(num_target_edges);
116        let mut edge_weights = Vec::with_capacity(num_target_edges);
117        let mut required_edges = Vec::with_capacity(n);
118
119        // Required edges: {v_i^a, v_i^b} = {2i, 2i+1} with weight 1
120        for i in 0..n {
121            target_edges.push((2 * i, 2 * i + 1));
122            edge_weights.push(1);
123            required_edges.push(i); // edge index i is required
124        }
125
126        // Connectivity edges for each source edge {v_i, v_j}:
127        //   {v_i^b, v_j^a} = {2i+1, 2j} with weight 1
128        //   {v_j^b, v_i^a} = {2j+1, 2i} with weight 1
129        for &(vi, vj) in &source_edges {
130            target_edges.push((2 * vi + 1, 2 * vj));
131            edge_weights.push(1);
132            target_edges.push((2 * vj + 1, 2 * vi));
133            edge_weights.push(1);
134        }
135
136        let target_graph = SimpleGraph::new(2 * n, target_edges);
137        let target = RuralPostman::new(target_graph, edge_weights, required_edges);
138
139        ReductionHamiltonianCircuitToRuralPostman {
140            target,
141            n,
142            source_edges,
143        }
144    }
145}
146
147#[cfg(feature = "example-db")]
148pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
149    use crate::export::SolutionPair;
150
151    vec![crate::example_db::specs::RuleExampleSpec {
152        id: "hamiltoniancircuit_to_ruralpostman",
153        build: || {
154            // Triangle graph: 3 vertices, 3 edges, HC = [0, 1, 2]
155            let source = HamiltonianCircuit::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]));
156
157            // Target graph has 6 vertices, 3 + 6 = 9 edges, 3 required edges.
158            // HC [0, 1, 2] uses connectivity edges:
159            //   0->1: fwd edge of source edge 0=(0,1), idx=3
160            //   1->2: fwd edge of source edge 1=(1,2), idx=5
161            //   2->0: bwd edge of source edge 2=(0,2), idx=8
162            // Required edges all have multiplicity 1.
163            // target_config = [1, 1, 1, 1, 0, 1, 0, 0, 1]
164            crate::example_db::specs::rule_example_with_witness::<_, RuralPostman<SimpleGraph, i32>>(
165                source,
166                SolutionPair {
167                    source_config: vec![0, 1, 2],
168                    target_config: vec![1, 1, 1, 1, 0, 1, 0, 0, 1],
169                },
170            )
171        },
172    }]
173}
174
175#[cfg(test)]
176#[path = "../unit_tests/rules/hamiltoniancircuit_ruralpostman.rs"]
177mod tests;