problemreductions/rules/
hamiltonianpath_ilp.rs1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
10use crate::models::graph::HamiltonianPath;
11use crate::reduction;
12use crate::rules::ilp_helpers::{
13 mccormick_product, one_hot_assignment_constraints, one_hot_decode,
14};
15use crate::rules::traits::{ReduceTo, ReductionResult};
16use crate::topology::{Graph, SimpleGraph};
17
18#[derive(Debug, Clone)]
25pub struct ReductionHamiltonianPathToILP {
26 target: ILP<bool>,
27 num_vertices: usize,
28}
29
30impl ReductionResult for ReductionHamiltonianPathToILP {
31 type Source = HamiltonianPath<SimpleGraph>;
32 type Target = ILP<bool>;
33
34 fn target_problem(&self) -> &ILP<bool> {
35 &self.target
36 }
37
38 fn extract_solution(
39 &self,
40 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
41 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
42 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
43
44 one_hot_decode(target_solution, self.num_vertices, self.num_vertices, 0)
45 }
46}
47
48#[reduction(
49 transform = upper_bound {
50 num_vars = "num_vertices^2 + 2 * num_edges * num_vertices",
51 num_constraints = "2 * num_vertices + 6 * num_edges * num_vertices + num_vertices",
52 },
53 unavailable = {
54 num_nonzeros = "the exact target parameter is not represented by this reduction's symbolic transform",
55 }
56)]
57impl ReduceTo<ILP<bool>> for HamiltonianPath<SimpleGraph> {
58 type Result = ReductionHamiltonianPathToILP;
59
60 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
61 let n = self.num_vertices();
62 let graph = self.graph();
63 let edges = graph.edges();
64 let m = edges.len();
65 let n_pos = if n == 0 { 0 } else { n - 1 }; let num_x = n * n;
68 let num_z = 2 * m * n_pos;
69 let num_vars = num_x + num_z;
70
71 let x_idx = |v: usize, p: usize| -> usize { v * n + p };
72 let z_fwd_idx = |e: usize, p: usize| -> usize { num_x + 2 * (e * n_pos + p) };
73 let z_rev_idx = |e: usize, p: usize| -> usize { num_x + 2 * (e * n_pos + p) + 1 };
74
75 let mut constraints = Vec::new();
76
77 constraints.extend(one_hot_assignment_constraints(n, n, 0));
79
80 for (e, &(u, v)) in edges.iter().enumerate() {
82 for p in 0..n_pos {
83 constraints.extend(mccormick_product(
85 z_fwd_idx(e, p),
86 x_idx(u, p),
87 x_idx(v, p + 1),
88 ));
89 constraints.extend(mccormick_product(
91 z_rev_idx(e, p),
92 x_idx(v, p),
93 x_idx(u, p + 1),
94 ));
95 }
96 }
97
98 for p in 0..n_pos {
100 let mut terms = Vec::new();
101 for e in 0..m {
102 terms.push((z_fwd_idx(e, p), 1));
103 terms.push((z_rev_idx(e, p), 1));
104 }
105 constraints.push(LinearConstraint::eq(terms, 1));
106 }
107
108 let target = ILP::new(num_vars, constraints, vec![], ObjectiveSense::Minimize)
110 .map_err(<Self as ReduceTo<ILP<bool>>>::target_construction)?;
111
112 Ok(ReductionHamiltonianPathToILP {
113 target,
114 num_vertices: n,
115 })
116 }
117}
118
119#[cfg(feature = "example-db")]
120pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
121 vec![crate::example_db::specs::RuleExampleSpec {
122 id: "hamiltonianpath_to_ilp",
123 build: || {
124 let source = HamiltonianPath::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]));
126 crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
127 },
128 }]
129}
130
131#[cfg(test)]
132#[path = "../unit_tests/rules/hamiltonianpath_ilp.rs"]
133mod tests;