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(&self, target_solution: &[usize]) -> Vec<usize> {
38 let loc = self.num_locations;
39 (0..self.num_facilities)
40 .map(|i| {
41 (0..loc)
42 .find(|&p| target_solution[i * loc + p] == 1)
43 .unwrap_or(0)
44 })
45 .collect()
46 }
47}
48
49#[reduction(
50 overhead = {
51 num_vars = "num_facilities * num_locations + num_facilities^2 * num_locations^2",
52 num_constraints = "num_facilities + num_locations + 3 * num_facilities^2 * num_locations^2",
53 }
54)]
55impl ReduceTo<ILP<bool>> for QuadraticAssignment {
56 type Result = ReductionQAPToILP;
57
58 fn reduce_to(&self) -> Self::Result {
59 let n = self.num_facilities();
60 let loc = self.num_locations();
61 let cost = self.cost_matrix();
62 let dist = self.distance_matrix();
63
64 let num_x = n * loc;
65
66 let x_idx = |i: usize, p: usize| -> usize { i * loc + p };
67
68 let mut z_pairs = Vec::new();
70 for i in 0..n {
71 for j in 0..n {
72 if i == j {
73 continue;
74 }
75 for p in 0..loc {
76 for q in 0..loc {
77 z_pairs.push((i, p, j, q));
78 }
79 }
80 }
81 }
82
83 let num_z = z_pairs.len();
84 let num_vars = num_x + num_z;
85
86 let z_idx = |z_seq: usize| -> usize { num_x + z_seq };
87
88 let mut constraints = Vec::new();
89
90 constraints.extend(one_hot_assignment_constraints(n, loc, 0));
92
93 for (z_seq, &(i, p, j, q)) in z_pairs.iter().enumerate() {
95 constraints.extend(mccormick_product(z_idx(z_seq), x_idx(i, p), x_idx(j, q)));
96 }
97
98 let mut objective = Vec::new();
100 for (z_seq, &(i, p, j, q)) in z_pairs.iter().enumerate() {
101 let coeff = cost[i][j] as f64 * dist[p][q] as f64;
102 if coeff != 0.0 {
103 objective.push((z_idx(z_seq), coeff));
104 }
105 }
106
107 let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Minimize);
108
109 ReductionQAPToILP {
110 target,
111 num_facilities: n,
112 num_locations: loc,
113 }
114 }
115}
116
117#[cfg(feature = "example-db")]
118pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
119 vec![crate::example_db::specs::RuleExampleSpec {
120 id: "quadraticassignment_to_ilp",
121 build: || {
122 let source = QuadraticAssignment::new(
124 vec![vec![0, 1], vec![1, 0]],
125 vec![vec![0, 2], vec![2, 0]],
126 );
127 crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
128 },
129 }]
130}
131
132#[cfg(test)]
133#[path = "../unit_tests/rules/quadraticassignment_ilp.rs"]
134mod tests;