problemreductions/rules/
quadraticassignment_ilp.rs1use crate::models::algebraic::QuadraticAssignment;
11use crate::models::algebraic::{ObjectiveSense, ILP};
12use crate::reduction;
13use crate::rules::ilp_helpers::{mccormick_product, one_hot_assignment_constraints};
14use crate::rules::traits::{ReduceTo, ReductionResult};
15
16#[derive(Debug, Clone)]
22pub struct ReductionQAPToILP {
23 target: ILP<bool>,
24 num_facilities: usize,
25 num_locations: usize,
26}
27
28impl ReductionResult for ReductionQAPToILP {
29 type Source = QuadraticAssignment;
30 type Target = ILP<bool>;
31
32 fn target_problem(&self) -> &ILP<bool> {
33 &self.target
34 }
35
36 fn extract_solution(
38 &self,
39 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
40 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
41 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
42
43 crate::rules::ilp_helpers::one_hot_decode_rows(
44 target_solution,
45 self.num_facilities,
46 self.num_locations,
47 0,
48 )
49 }
50}
51
52#[reduction(
53 transform = upper_bound {
54 num_vars = "num_facilities * num_locations + num_facilities^2 * num_locations^2",
55 num_constraints = "num_facilities + num_locations + 3 * num_facilities^2 * num_locations^2",
56 },
57 unavailable = {
58 num_nonzeros = "the exact target parameter is not represented by this reduction's symbolic transform",
59 }
60)]
61impl ReduceTo<ILP<bool>> for QuadraticAssignment {
62 type Result = ReductionQAPToILP;
63
64 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
65 let n = self.num_facilities();
66 let loc = self.num_locations();
67 let cost = self.cost_matrix();
68 let dist = self.distance_matrix();
69
70 let num_x = n * loc;
71
72 let x_idx = |i: usize, p: usize| -> usize { i * loc + p };
73
74 let mut z_pairs = Vec::new();
76 for i in 0..n {
77 for j in 0..n {
78 if i == j {
79 continue;
80 }
81 for p in 0..loc {
82 for q in 0..loc {
83 z_pairs.push((i, p, j, q));
84 }
85 }
86 }
87 }
88
89 let num_z = z_pairs.len();
90 let num_vars = num_x + num_z;
91
92 let z_idx = |z_seq: usize| -> usize { num_x + z_seq };
93
94 let mut constraints = Vec::new();
95
96 constraints.extend(one_hot_assignment_constraints(n, loc, 0));
98
99 for (z_seq, &(i, p, j, q)) in z_pairs.iter().enumerate() {
101 constraints.extend(mccormick_product(z_idx(z_seq), x_idx(i, p), x_idx(j, q)));
102 }
103
104 let mut objective = Vec::new();
106 for (z_seq, &(i, p, j, q)) in z_pairs.iter().enumerate() {
107 let coefficient = cost[i][j].checked_mul(dist[p][q]).ok_or_else(|| {
108 crate::rules::ReductionError::integer_overflow::<QuadraticAssignment, ILP<bool>>(
109 "multiplying a quadratic-assignment cost by a distance",
110 )
111 })?;
112 let coeff = coefficient;
113 if coeff != 0 {
114 objective.push((z_idx(z_seq), coeff));
115 }
116 }
117
118 let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize)
119 .map_err(Self::target_construction)?;
120
121 Ok(ReductionQAPToILP {
122 target,
123 num_facilities: n,
124 num_locations: loc,
125 })
126 }
127}
128
129#[cfg(feature = "example-db")]
130pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
131 vec![crate::example_db::specs::RuleExampleSpec {
132 id: "quadraticassignment_to_ilp",
133 build: || {
134 let source = QuadraticAssignment::new(
136 vec![vec![0, 1], vec![1, 0]],
137 vec![vec![0, 2], vec![2, 0]],
138 );
139 crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
140 },
141 }]
142}
143
144#[cfg(test)]
145#[path = "../unit_tests/rules/quadraticassignment_ilp.rs"]
146mod tests;