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;