problemreductions/rules/
hamiltonianpath_ilp.rs1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
4use crate::models::graph::HamiltonianPath;
5use crate::reduction;
6use crate::rules::ilp_helpers::{one_hot_assignment_constraints, one_hot_decode};
7use crate::rules::traits::{ReduceTo, ReductionResult};
8use crate::topology::{Graph, SimpleGraph};
9
10#[derive(Debug, Clone)]
12pub struct ReductionHamiltonianPathToILP {
13 target: ILP<bool>,
14 num_vertices: usize,
15}
16
17impl ReductionResult for ReductionHamiltonianPathToILP {
18 type Source = HamiltonianPath<SimpleGraph>;
19 type Target = ILP<bool>;
20
21 fn target_problem(&self) -> &ILP<bool> {
22 &self.target
23 }
24
25 fn extract_solution(
26 &self,
27 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
28 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
29 crate::rules::traits::validate_target_witness(
30 self.target_problem(),
31 target_solution,
32 |value| value.value.is_some(),
33 "target ILP assignment is infeasible",
34 )?;
35
36 one_hot_decode(target_solution, self.num_vertices, self.num_vertices, 0)
37 }
38}
39
40#[crate::aggregate_reduction(ilp_feasibility)]
41impl crate::rules::AggregateReductionResult for ReductionHamiltonianPathToILP {}
42
43#[reduction(transform = {
44 exact {
45 max_constraint_magnitude_bits = "1",
46 num_vars = "num_vertices^2",
47 num_constraints = "num_vertices^2 + num_vertices",
48 },
49 upper_bound {
50 num_nonzeros = "3 * num_vertices^2 + 2 * num_vertices * num_edges",
51 },
52})]
53impl ReduceTo<ILP<bool>> for HamiltonianPath<SimpleGraph> {
54 type Result = ReductionHamiltonianPathToILP;
55
56 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
57 let n = self.num_vertices();
58 let num_vars = n.checked_mul(n).ok_or_else(|| {
59 crate::rules::ReductionError::integer_overflow::<Self, ILP<bool>>(
60 "counting Hamiltonian permutation variables",
61 )
62 })?;
63 <Self as ReduceTo<ILP<bool>>>::exact_i64(n, "bounding adjacency row sums")?;
64 let mut neighbors = vec![Vec::new(); n];
65 for (u, v) in self.graph().edges() {
66 if u != v {
67 neighbors[u].push(v);
68 neighbors[v].push(u);
69 }
70 }
71 let mut constraints = one_hot_assignment_constraints(n, n, 0);
72 for (v, adjacent) in neighbors.iter_mut().enumerate() {
73 adjacent.sort_unstable();
74 adjacent.dedup();
75 for p in 0..n.saturating_sub(1) {
76 let mut terms = vec![(v * n + p, 1)];
77 terms.extend(adjacent.iter().map(|&w| (w * n + p + 1, -1)));
78 constraints.push(LinearConstraint::le(terms, 0));
79 }
80 }
81
82 let target = ILP::new(num_vars, constraints, vec![], ObjectiveSense::Minimize)
84 .map_err(<Self as ReduceTo<ILP<bool>>>::target_construction)?;
85
86 Ok(ReductionHamiltonianPathToILP {
87 target,
88 num_vertices: n,
89 })
90 }
91}
92
93#[cfg(feature = "example-db")]
94pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
95 vec![crate::example_db::specs::RuleExampleSpec {
96 id: "hamiltonianpath_to_ilp",
97 build: || {
98 let source = HamiltonianPath::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]));
100 crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
101 },
102 }]
103}
104
105#[cfg(test)]
106#[path = "../unit_tests/rules/hamiltonianpath_ilp.rs"]
107mod tests;