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;