1use crate::registry::{CreateSpec, ProblemSchemaEntry};
8use crate::traits::Problem;
9use serde::{Deserialize, Serialize};
10
11inventory::submit! {
12 ProblemSchemaEntry {
13 name: "TimetableDesign",
14 display_name: "Timetable Design",
15 aliases: &[],
16 dimensions: &[],
17 category: crate::registry::ProblemCategory::Misc,
18 module_path: module_path!(),
19 description: "Assign craftsmen to tasks over work periods subject to availability and exact pairwise requirements",
20 fields: TimetableDesignCreateSpec::FIELDS,
21 }
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
30#[serde(try_from = "TimetableDesignCreateSpec")]
31pub struct TimetableDesign {
32 num_periods: usize,
33 num_craftsmen: usize,
34 num_tasks: usize,
35 craftsman_avail: Vec<Vec<bool>>,
36 task_avail: Vec<Vec<bool>>,
37 requirements: Vec<Vec<i64>>,
38}
39
40#[derive(Debug, Deserialize, crate::CreateSpec)]
41struct TimetableDesignCreateSpec {
42 num_periods: usize,
44 num_craftsmen: usize,
46 num_tasks: usize,
48 craftsman_avail: Vec<Vec<bool>>,
50 task_avail: Vec<Vec<bool>>,
52 requirements: Vec<Vec<i64>>,
54}
55impl TryFrom<TimetableDesignCreateSpec> for TimetableDesign {
56 type Error = crate::registry::ConstructionError;
57 fn try_from(spec: TimetableDesignCreateSpec) -> Result<Self, Self::Error> {
58 Self::try_new(
59 spec.num_periods,
60 spec.num_craftsmen,
61 spec.num_tasks,
62 spec.craftsman_avail,
63 spec.task_avail,
64 spec.requirements,
65 )
66 }
67}
68
69impl TimetableDesign {
70 pub fn new(
76 num_periods: usize,
77 num_craftsmen: usize,
78 num_tasks: usize,
79 craftsman_avail: Vec<Vec<bool>>,
80 task_avail: Vec<Vec<bool>>,
81 requirements: Vec<Vec<i64>>,
82 ) -> Self {
83 Self::try_new(
84 num_periods,
85 num_craftsmen,
86 num_tasks,
87 craftsman_avail,
88 task_avail,
89 requirements,
90 )
91 .unwrap_or_else(|error| panic!("{error}"))
92 }
93
94 fn try_new(
95 num_periods: usize,
96 num_craftsmen: usize,
97 num_tasks: usize,
98 craftsman_avail: Vec<Vec<bool>>,
99 task_avail: Vec<Vec<bool>>,
100 requirements: Vec<Vec<i64>>,
101 ) -> Result<Self, crate::registry::ConstructionError> {
102 if craftsman_avail.len() != num_craftsmen {
103 return Err(format!(
104 "craftsman_avail has {} rows, expected {}",
105 craftsman_avail.len(),
106 num_craftsmen
107 )
108 .into());
109 }
110 for (craftsman, row) in craftsman_avail.iter().enumerate() {
111 if row.len() != num_periods {
112 return Err(format!(
113 "craftsman {} availability has {} periods, expected {}",
114 craftsman,
115 row.len(),
116 num_periods
117 )
118 .into());
119 }
120 }
121
122 if task_avail.len() != num_tasks {
123 return Err(format!(
124 "task_avail has {} rows, expected {}",
125 task_avail.len(),
126 num_tasks
127 )
128 .into());
129 }
130 for (task, row) in task_avail.iter().enumerate() {
131 if row.len() != num_periods {
132 return Err(format!(
133 "task {} availability has {} periods, expected {}",
134 task,
135 row.len(),
136 num_periods
137 )
138 .into());
139 }
140 }
141
142 if requirements.len() != num_craftsmen {
143 return Err(format!(
144 "requirements has {} rows, expected {}",
145 requirements.len(),
146 num_craftsmen
147 )
148 .into());
149 }
150 for (craftsman, row) in requirements.iter().enumerate() {
151 if row.len() != num_tasks {
152 return Err(format!(
153 "requirements row {} has {} tasks, expected {}",
154 craftsman,
155 row.len(),
156 num_tasks
157 )
158 .into());
159 }
160 }
161
162 Ok(Self {
163 num_periods,
164 num_craftsmen,
165 num_tasks,
166 craftsman_avail,
167 task_avail,
168 requirements,
169 })
170 }
171
172 pub fn num_periods(&self) -> usize {
174 self.num_periods
175 }
176
177 pub fn num_craftsmen(&self) -> usize {
179 self.num_craftsmen
180 }
181
182 pub fn num_tasks(&self) -> usize {
184 self.num_tasks
185 }
186
187 pub fn craftsman_avail(&self) -> &[Vec<bool>] {
189 &self.craftsman_avail
190 }
191
192 pub fn task_avail(&self) -> &[Vec<bool>] {
194 &self.task_avail
195 }
196
197 pub fn requirements(&self) -> &[Vec<i64>] {
199 &self.requirements
200 }
201
202 fn config_len(&self) -> usize {
203 self.num_craftsmen * self.num_tasks * self.num_periods
204 }
205
206 fn index(&self, craftsman: usize, task: usize, period: usize) -> usize {
207 ((craftsman * self.num_tasks) + task) * self.num_periods + period
208 }
209
210 pub fn num_available_assignments(&self) -> usize {
212 self.requirements
213 .iter()
214 .enumerate()
215 .map(|(c, row)| {
216 row.iter()
217 .enumerate()
218 .filter(|(_, r)| **r > 0)
219 .map(|(t, _)| {
220 self.craftsman_avail[c]
221 .iter()
222 .zip(&self.task_avail[t])
223 .filter(|(a, b)| **a && **b)
224 .count()
225 })
226 .sum::<usize>()
227 })
228 .sum()
229 }
230
231 pub fn num_nonzero_requirements(&self) -> usize {
233 self.requirements
234 .iter()
235 .flatten()
236 .filter(|&&r| r != 0)
237 .count()
238 }
239
240 pub fn period_count_bits(&self) -> u64 {
242 crate::types::max_numeric_magnitude_bits([self.num_periods])
243 }
244}
245
246impl Problem for TimetableDesign {
247 const NAME: &'static str = "TimetableDesign";
248 type Solution = Vec<Vec<Vec<bool>>>;
249 type Value = crate::types::Or;
250
251 crate::problem_parameters![
252 ("num_craftsmen", num_craftsmen),
253 ("num_available_assignments", num_available_assignments),
254 ("num_nonzero_requirements", num_nonzero_requirements),
255 ("period_count_bits", period_count_bits),
256 ("num_periods", num_periods),
257 ("num_tasks", num_tasks),
258 ];
259
260 fn evaluate(
261 &self,
262 solution: &Self::Solution,
263 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
264 if solution.len() != self.num_craftsmen
265 || solution.iter().any(|craftsman| {
266 craftsman.len() != self.num_tasks
267 || craftsman.iter().any(|task| task.len() != self.num_periods)
268 })
269 {
270 return Err(crate::traits::EvaluationError::InvalidConfiguration(
271 "timetable dimensions do not match the instance".into(),
272 ));
273 }
274 let config = solution
275 .iter()
276 .flatten()
277 .flatten()
278 .copied()
279 .collect::<Vec<_>>();
280 Ok({
281 crate::types::Or({
282 if config.len() != self.config_len() {
283 return Ok(crate::types::Or(false));
284 }
285 let mut craftsman_busy = vec![vec![false; self.num_periods]; self.num_craftsmen];
286 let mut task_busy = vec![vec![false; self.num_periods]; self.num_tasks];
287 let mut pair_counts = vec![vec![0i64; self.num_tasks]; self.num_craftsmen];
288
289 for craftsman in 0..self.num_craftsmen {
290 for task in 0..self.num_tasks {
291 for period in 0..self.num_periods {
292 if !config[self.index(craftsman, task, period)] {
293 continue;
294 }
295
296 if !self.craftsman_avail[craftsman][period]
297 || !self.task_avail[task][period]
298 {
299 return Ok(crate::types::Or(false));
300 }
301
302 if craftsman_busy[craftsman][period] || task_busy[task][period] {
303 return Ok(crate::types::Or(false));
304 }
305
306 craftsman_busy[craftsman][period] = true;
307 task_busy[task][period] = true;
308 pair_counts[craftsman][task] += 1;
309 }
310 }
311 }
312
313 pair_counts == self.requirements
314 })
315 })
316 }
317
318 fn variant() -> Vec<(&'static str, &'static str)> {
319 crate::variant_params![]
320 }
321}
322
323impl crate::solvers::BruteForceProblem for TimetableDesign {
324 fn dimensions(&self) -> Vec<usize> {
325 vec![2; self.config_len()]
326 }
327}
328
329crate::declare_variants! {
330 default TimetableDesign => "2^(num_craftsmen * num_tasks * num_periods)" create TimetableDesignCreateSpec,
331}
332
333crate::register_brute_force! {
334 TimetableDesign decode |problem: &TimetableDesign, indices: Vec<usize>| (0..problem.num_craftsmen()).map(|craftsman| (0..problem.num_tasks()).map(|task| (0..problem.num_periods()).map(|period| indices[problem.index(craftsman, task, period)] != 0).collect()).collect()).collect(),
335}
336
337#[cfg(any(test, feature = "example-db"))]
338const ISSUE_EXAMPLE_ASSIGNMENTS: &[(usize, usize, usize)] = &[
339 (0, 0, 0),
340 (1, 4, 0),
341 (1, 1, 1),
342 (2, 3, 1),
343 (0, 2, 2),
344 (3, 4, 2),
345 (4, 1, 2),
346];
347
348#[cfg(any(test, feature = "example-db"))]
349fn issue_example_problem() -> TimetableDesign {
350 TimetableDesign::new(
351 3,
352 5,
353 5,
354 vec![
355 vec![true, true, true],
356 vec![true, true, false],
357 vec![false, true, true],
358 vec![true, false, true],
359 vec![true, true, true],
360 ],
361 vec![
362 vec![true, true, false],
363 vec![false, true, true],
364 vec![true, false, true],
365 vec![true, true, true],
366 vec![true, true, true],
367 ],
368 vec![
369 vec![1, 0, 1, 0, 0],
370 vec![0, 1, 0, 0, 1],
371 vec![0, 0, 0, 1, 0],
372 vec![0, 0, 0, 0, 1],
373 vec![0, 1, 0, 0, 0],
374 ],
375 )
376}
377
378#[cfg(any(test, feature = "example-db"))]
379fn issue_example_config() -> Vec<Vec<Vec<bool>>> {
380 let problem = issue_example_problem();
381 let mut config = vec![
382 vec![vec![false; problem.num_periods()]; problem.num_tasks()];
383 problem.num_craftsmen()
384 ];
385 for &(craftsman, task, period) in ISSUE_EXAMPLE_ASSIGNMENTS {
386 config[craftsman][task][period] = true;
387 }
388 config
389}
390
391#[cfg(feature = "example-db")]
392pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
393 vec![crate::example_db::specs::ModelExampleSpec {
394 id: "timetable_design",
395 instance: Box::new(issue_example_problem()),
396 optimal_config: serde_json::json!(issue_example_config()),
397 optimal_value: serde_json::json!(true),
398 }]
399}
400
401#[cfg(test)]
402#[path = "../../unit_tests/models/misc/timetable_design.rs"]
403mod tests;