problemreductions/rules/
hamiltoniancircuit_ilp.rs1use crate::models::algebraic::{Bounded, IntegerVariable, LinearConstraint, ObjectiveSense, ILP};
4use crate::models::graph::HamiltonianCircuit;
5use crate::rules::traits::{ReduceTo, ReductionResult};
6use crate::topology::{Graph, SimpleGraph};
7
8type Target = ILP<i64, i64, Bounded>;
9
10#[derive(Debug, Clone)]
11pub struct ReductionHamiltonianCircuitToILP {
12 target: Target,
13 graph: SimpleGraph,
14}
15
16impl ReductionResult for ReductionHamiltonianCircuitToILP {
17 type Source = HamiltonianCircuit<SimpleGraph>;
18 type Target = Target;
19
20 fn target_problem(&self) -> &Target {
21 &self.target
22 }
23
24 fn extract_solution(&self, solution: &Vec<i64>) -> crate::rules::ExtractionResult<Vec<usize>> {
25 crate::rules::traits::validate_target_witness(
26 self.target_problem(),
27 solution,
28 |value| value.value.is_some(),
29 "target ILP assignment is infeasible",
30 )?;
31 let selected: Vec<_> = solution[..self.graph.num_edges()]
32 .iter()
33 .map(|&value| value == 1)
34 .collect();
35 crate::rules::graph_helpers::edges_to_cycle_order(&self.graph, &selected)
36 }
37}
38
39#[crate::aggregate_reduction(ilp_feasibility)]
40impl crate::rules::AggregateReductionResult for ReductionHamiltonianCircuitToILP {}
41
42#[crate::reduction(transform = upper_bound {
43 num_vars = "3 * num_edges",
44 num_constraints = "2 * num_vertices + 2 * num_edges + 1",
45 num_nonzeros = "10 * num_edges",
46 max_constraint_magnitude_bits = "num_vertices + 2",
47})]
48impl ReduceTo<ILP<i64, i64, Bounded>> for HamiltonianCircuit<SimpleGraph> {
49 type Result = ReductionHamiltonianCircuitToILP;
50 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
51 let n = self.num_vertices();
52 let mut edges: Vec<_> = self
53 .graph()
54 .edges()
55 .into_iter()
56 .filter(|(u, v)| u != v)
57 .map(|(u, v)| (u.min(v), u.max(v)))
58 .collect();
59 edges.sort_unstable();
60 edges.dedup();
61 let m = edges.len();
62 let count =
63 m.checked_mul(3).ok_or(
64 crate::rules::ReductionError::integer_overflow::<Self, Target>(
65 "counting circuit flow variables",
66 ),
67 )?;
68 let capacity = <Self as ReduceTo<Target>>::exact_i64(
69 n.saturating_sub(1),
70 "bounding spanning-cycle flow",
71 )?;
72 let mut variables = vec![IntegerVariable::binary(); m];
73 variables.resize(
74 count,
75 IntegerVariable::new(Some(0), Some(capacity))
76 .map_err(<Self as ReduceTo<Target>>::target_construction)?,
77 );
78 let mut degree = vec![Vec::new(); n];
79 let mut balance = vec![Vec::new(); n];
80 let mut constraints = Vec::new();
81 for (edge, &(u, v)) in edges.iter().enumerate() {
82 degree[u].push((edge, 1));
83 degree[v].push((edge, 1));
84 for (direction, (from, to)) in [(u, v), (v, u)].into_iter().enumerate() {
85 let flow = m + 2 * edge + direction;
86 balance[from].push((flow, 1));
87 balance[to].push((flow, -1));
88 constraints.push(LinearConstraint::le(vec![(flow, 1), (edge, -capacity)], 0));
89 }
90 }
91 for (v, (degree, balance)) in degree.into_iter().zip(balance).enumerate() {
92 constraints.push(LinearConstraint::eq(degree, 2));
93 constraints.push(LinearConstraint::eq(
94 balance,
95 if v == 0 { capacity } else { -1 },
96 ));
97 }
98 if n < 3 {
99 constraints.push(LinearConstraint::eq(vec![], 1));
100 }
101 let target =
102 Target::with_variables(variables, constraints, vec![], ObjectiveSense::Minimize)
103 .map_err(<Self as ReduceTo<Target>>::target_construction)?;
104 Ok(ReductionHamiltonianCircuitToILP {
105 target,
106 graph: SimpleGraph::new(n, edges),
107 })
108 }
109}
110
111#[cfg(feature = "example-db")]
112pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
113 vec![crate::example_db::specs::RuleExampleSpec {
114 id: "hamiltoniancircuit_to_ilp",
115 build: || {
116 crate::example_db::specs::rule_example_via_bounded_ilp(HamiltonianCircuit::new(
117 SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3), (0, 3)]),
118 ))
119 },
120 }]
121}
122
123#[cfg(test)]
124#[path = "../unit_tests/rules/hamiltoniancircuit_ilp.rs"]
125mod tests;