problemreductions/models/algebraic/
quadratic_assignment.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
7use crate::traits::Problem;
8use crate::types::Min;
9use serde::{Deserialize, Serialize};
10
11inventory::submit! {
12 ProblemSchemaEntry {
13 name: "QuadraticAssignment",
14 display_name: "Quadratic Assignment",
15 aliases: &["QAP"],
16 dimensions: &[],
17 module_path: module_path!(),
18 description: "Minimize total cost of assigning facilities to locations",
19 fields: &[
20 FieldInfo { name: "cost_matrix", type_name: "Vec<Vec<i64>>", description: "Flow/cost matrix between facilities" },
21 FieldInfo { name: "distance_matrix", type_name: "Vec<Vec<i64>>", description: "Distance matrix between locations" },
22 ],
23 }
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct QuadraticAssignment {
61 cost_matrix: Vec<Vec<i64>>,
63 distance_matrix: Vec<Vec<i64>>,
65}
66
67impl QuadraticAssignment {
68 pub fn new(cost_matrix: Vec<Vec<i64>>, distance_matrix: Vec<Vec<i64>>) -> Self {
77 let n = cost_matrix.len();
78 for row in &cost_matrix {
79 assert_eq!(row.len(), n, "cost_matrix must be square");
80 }
81 let m = distance_matrix.len();
82 for row in &distance_matrix {
83 assert_eq!(row.len(), m, "distance_matrix must be square");
84 }
85 assert!(
86 n <= m,
87 "num_facilities ({n}) must be <= num_locations ({m})"
88 );
89 Self {
90 cost_matrix,
91 distance_matrix,
92 }
93 }
94
95 pub fn cost_matrix(&self) -> &[Vec<i64>] {
97 &self.cost_matrix
98 }
99
100 pub fn distance_matrix(&self) -> &[Vec<i64>] {
102 &self.distance_matrix
103 }
104
105 pub fn num_facilities(&self) -> usize {
107 self.cost_matrix.len()
108 }
109
110 pub fn num_locations(&self) -> usize {
112 self.distance_matrix.len()
113 }
114}
115
116impl Problem for QuadraticAssignment {
117 const NAME: &'static str = "QuadraticAssignment";
118 type Value = Min<i64>;
119
120 fn dims(&self) -> Vec<usize> {
121 vec![self.num_locations(); self.num_facilities()]
122 }
123
124 fn evaluate(&self, config: &[usize]) -> Min<i64> {
125 let n = self.num_facilities();
126 let m = self.num_locations();
127
128 if config.len() != n {
130 return Min(None);
131 }
132
133 for &loc in config {
135 if loc >= m {
136 return Min(None);
137 }
138 }
139
140 let mut used = vec![false; m];
142 for &loc in config {
143 if used[loc] {
144 return Min(None);
145 }
146 used[loc] = true;
147 }
148
149 let mut total: i64 = 0;
151 for i in 0..n {
152 for j in 0..n {
153 if i != j {
154 total += self.cost_matrix[i][j] * self.distance_matrix[config[i]][config[j]];
155 }
156 }
157 }
158
159 Min(Some(total))
160 }
161
162 fn variant() -> Vec<(&'static str, &'static str)> {
163 crate::variant_params![]
164 }
165}
166
167crate::declare_variants! {
168 default QuadraticAssignment => "factorial(num_facilities)",
169}
170
171#[cfg(feature = "example-db")]
172pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
173 vec![crate::example_db::specs::ModelExampleSpec {
174 id: "quadratic_assignment",
175 instance: Box::new(QuadraticAssignment::new(
176 vec![
177 vec![0, 5, 2, 0],
178 vec![5, 0, 0, 3],
179 vec![2, 0, 0, 4],
180 vec![0, 3, 4, 0],
181 ],
182 vec![
183 vec![0, 4, 1, 1],
184 vec![4, 0, 3, 4],
185 vec![1, 3, 0, 4],
186 vec![1, 4, 4, 0],
187 ],
188 )),
189 optimal_config: vec![3, 0, 1, 2],
190 optimal_value: serde_json::json!(56),
191 }]
192}
193
194#[cfg(test)]
195#[path = "../../unit_tests/models/algebraic/quadratic_assignment.rs"]
196mod tests;