1use crate::models::formula::{CNFClause, KSatisfiability};
24use crate::models::misc::TimetableDesign;
25use crate::reduction;
26use crate::rules::traits::{ReduceTo, ReductionResult};
27#[cfg(any(test, feature = "example-db"))]
28use crate::traits::Problem;
29use crate::variant::K3;
30use std::collections::VecDeque;
31
32#[derive(Debug, Clone)]
33struct NormalizedFormula {
34 clauses: Vec<CNFClause>,
35 transformed_to_original: Vec<usize>,
36 pure_assignments: Vec<Option<usize>>,
37 source_num_vars: usize,
38}
39
40#[derive(Debug, Clone)]
41struct CoreGraph {
42 edges: Vec<(usize, usize)>,
43 blocked_colors: Vec<Vec<usize>>,
44}
45
46impl CoreGraph {
47 fn new() -> Self {
48 Self {
49 edges: Vec::new(),
50 blocked_colors: Vec::new(),
51 }
52 }
53
54 fn add_vertex(&mut self) -> usize {
55 let id = self.blocked_colors.len();
56 self.blocked_colors.push(Vec::new());
57 id
58 }
59
60 fn add_edge(&mut self, u: usize, v: usize) -> usize {
61 self.edges.push((u, v));
62 self.edges.len() - 1
63 }
64
65 fn block_color(&mut self, vertex: usize, color: usize) {
66 let blocked = &mut self.blocked_colors[vertex];
67 if !blocked.contains(&color) {
68 blocked.push(color);
69 }
70 }
71}
72
73#[cfg_attr(not(any(test, feature = "example-db")), allow(dead_code))]
74#[derive(Debug, Clone)]
75enum EdgeEncoding {
76 Direct {
77 edge: usize,
78 allowed: Vec<usize>,
79 },
80 TwoList {
81 left_outer: usize,
82 middle: usize,
83 right_outer: usize,
84 first: usize,
85 second: usize,
86 },
87}
88
89#[cfg_attr(not(any(test, feature = "example-db")), allow(dead_code))]
90#[derive(Debug, Clone)]
91struct VariableEncoding {
92 vb: EdgeEncoding,
93 vd: EdgeEncoding,
94 ab: EdgeEncoding,
95 bc: EdgeEncoding,
96 cd: EdgeEncoding,
97 de: EdgeEncoding,
98 neg2: usize,
99}
100
101#[cfg_attr(not(any(test, feature = "example-db")), allow(dead_code))]
102#[derive(Debug, Clone)]
103struct ClauseEncoding {
104 edge: EdgeEncoding,
105}
106
107#[cfg_attr(not(any(test, feature = "example-db")), allow(dead_code))]
108#[derive(Debug, Clone)]
109struct ReductionLayout {
110 source_num_vars: usize,
111 num_periods: usize,
112 craftsman_avail: Vec<Vec<bool>>,
113 task_avail: Vec<Vec<bool>>,
114 requirements: Vec<Vec<u64>>,
115 pure_assignments: Vec<Option<usize>>,
116 transformed_to_original: Vec<usize>,
117 normalized_clauses: Vec<CNFClause>,
118 variable_encodings: Vec<VariableEncoding>,
119 clause_encodings: Vec<ClauseEncoding>,
120 edge_pairs: Vec<(usize, usize)>,
121}
122
123#[derive(Debug, Clone)]
125pub struct Reduction3SATToTimetableDesign {
126 target: TimetableDesign,
127 layout: ReductionLayout,
128}
129
130fn literal_var_index(literal: i32) -> usize {
131 literal.unsigned_abs() as usize - 1
132}
133
134#[cfg(any(test, feature = "example-db"))]
135fn evaluate_clause(clause: &CNFClause, assignment: &[usize]) -> bool {
136 clause.literals.iter().any(|&literal| {
137 let value = assignment[literal_var_index(literal)] == 1;
138 if literal > 0 {
139 value
140 } else {
141 !value
142 }
143 })
144}
145
146fn eliminate_pure_literals(source: &KSatisfiability<K3>) -> (Vec<CNFClause>, Vec<Option<usize>>) {
147 let mut clauses = source.clauses().to_vec();
148 let mut assignments = vec![None; source.num_vars()];
149
150 loop {
151 let mut positive = vec![0usize; source.num_vars()];
152 let mut negative = vec![0usize; source.num_vars()];
153 for clause in &clauses {
154 for &literal in &clause.literals {
155 let var = literal_var_index(literal);
156 if literal > 0 {
157 positive[var] += 1;
158 } else {
159 negative[var] += 1;
160 }
161 }
162 }
163
164 let mut changed = false;
165 for var in 0..source.num_vars() {
166 if assignments[var].is_some() {
167 continue;
168 }
169 match (positive[var] > 0, negative[var] > 0) {
170 (true, false) => {
171 assignments[var] = Some(1);
172 changed = true;
173 }
174 (false, true) => {
175 assignments[var] = Some(0);
176 changed = true;
177 }
178 _ => {}
179 }
180 }
181
182 if !changed {
183 break;
184 }
185
186 clauses.retain(|clause| {
187 !clause.literals.iter().any(|&literal| {
188 let var = literal_var_index(literal);
189 match assignments[var] {
190 Some(1) => literal > 0,
191 Some(0) => literal < 0,
192 None => false,
193 Some(_) => unreachable!(),
194 }
195 })
196 });
197 }
198
199 (clauses, assignments)
200}
201
202fn normalize_formula(source: &KSatisfiability<K3>) -> NormalizedFormula {
203 let (mut clauses, pure_assignments) = eliminate_pure_literals(source);
204 let source_num_vars = source.num_vars();
205 let mut transformed_to_original = Vec::new();
206 let mut next_var = source_num_vars + 1;
207
208 for original_var in 1..=source_num_vars {
209 let mut occurrences = Vec::new();
210 for (clause_idx, clause) in clauses.iter().enumerate() {
211 for (lit_idx, &literal) in clause.literals.iter().enumerate() {
212 if literal_var_index(literal) + 1 == original_var {
213 occurrences.push((clause_idx, lit_idx, literal > 0));
214 }
215 }
216 }
217
218 if occurrences.is_empty() {
219 continue;
220 }
221
222 if occurrences.len() <= 3 {
223 let replacement = next_var;
224 next_var += 1;
225 transformed_to_original.push(original_var - 1);
226 for (clause_idx, lit_idx, is_positive) in occurrences {
227 clauses[clause_idx].literals[lit_idx] = if is_positive {
228 replacement as i32
229 } else {
230 -(replacement as i32)
231 };
232 }
233 continue;
234 }
235
236 let replacements: Vec<usize> = (0..occurrences.len())
237 .map(|_| {
238 let id = next_var;
239 next_var += 1;
240 transformed_to_original.push(original_var - 1);
241 id
242 })
243 .collect();
244
245 for ((clause_idx, lit_idx, is_positive), replacement) in
246 occurrences.into_iter().zip(replacements.iter().copied())
247 {
248 clauses[clause_idx].literals[lit_idx] = if is_positive {
249 replacement as i32
250 } else {
251 -(replacement as i32)
252 };
253 }
254
255 for idx in 0..replacements.len() {
256 let current = replacements[idx] as i32;
257 let next = replacements[(idx + 1) % replacements.len()] as i32;
258 clauses.push(CNFClause::new(vec![current, -next]));
259 }
260 }
261
262 for clause in &mut clauses {
263 for literal in &mut clause.literals {
264 let sign = if *literal < 0 { -1 } else { 1 };
265 let temp_var = literal.unsigned_abs() as usize;
266 debug_assert!(
267 temp_var > source_num_vars,
268 "all residual literals should have been replaced by transformed variables"
269 );
270 let compact_var = temp_var - source_num_vars;
271 *literal = sign * compact_var as i32;
272 }
273 }
274
275 for transformed_var in 1..=transformed_to_original.len() {
276 let mut positive = 0usize;
277 let mut negative = 0usize;
278 for clause in &clauses {
279 for &literal in &clause.literals {
280 if literal_var_index(literal) + 1 == transformed_var {
281 if literal > 0 {
282 positive += 1;
283 } else {
284 negative += 1;
285 }
286 }
287 }
288 }
289 debug_assert!(
290 positive <= 2,
291 "normalized variable {transformed_var} has {positive} positive occurrences"
292 );
293 debug_assert!(
294 negative <= 2,
295 "normalized variable {transformed_var} has {negative} negative occurrences"
296 );
297 debug_assert!(
298 positive > 0 && negative > 0,
299 "pure literals should have been eliminated before gadget construction"
300 );
301 }
302
303 NormalizedFormula {
304 clauses,
305 transformed_to_original,
306 pure_assignments,
307 source_num_vars,
308 }
309}
310
311#[cfg(any(test, feature = "example-db"))]
312fn choose_clause_edge_color(
313 clause: &CNFClause,
314 assignment: &[usize],
315 colors: &[usize],
316) -> Option<usize> {
317 clause
318 .literals
319 .iter()
320 .zip(colors.iter().copied())
321 .find_map(|(&literal, color)| {
322 let value = assignment[literal_var_index(literal)] == 1;
323 let satisfied = if literal > 0 { value } else { !value };
324 satisfied.then_some(color)
325 })
326}
327
328fn add_two_list_edge(
329 graph: &mut CoreGraph,
330 all_colors: &[usize],
331 x: usize,
332 y: usize,
333 first: usize,
334 second: usize,
335) -> EdgeEncoding {
336 let x_prime = graph.add_vertex();
337 let y_prime = graph.add_vertex();
338
339 let left_outer = graph.add_edge(x, x_prime);
340 let middle = graph.add_edge(x_prime, y_prime);
341 let right_outer = graph.add_edge(y_prime, y);
342
343 for &color in all_colors {
344 if color != first && color != second {
345 graph.block_color(x_prime, color);
346 graph.block_color(y_prime, color);
347 }
348 }
349
350 EdgeEncoding::TwoList {
351 left_outer,
352 middle,
353 right_outer,
354 first,
355 second,
356 }
357}
358
359fn add_direct_clause_edge(
360 graph: &mut CoreGraph,
361 all_colors: &[usize],
362 x: usize,
363 y: usize,
364 allowed: Vec<usize>,
365) -> EdgeEncoding {
366 let edge = graph.add_edge(x, y);
367 for &color in all_colors {
368 if !allowed.contains(&color) {
369 graph.block_color(y, color);
370 }
371 }
372 EdgeEncoding::Direct { edge, allowed }
373}
374
375fn core_edge_color(
376 solution: &[usize],
377 pair: (usize, usize),
378 num_tasks: usize,
379 num_periods: usize,
380) -> usize {
381 (0..num_periods)
382 .find(|&period| solution[((pair.0 * num_tasks) + pair.1) * num_periods + period] == 1)
383 .expect("each required pair should be scheduled exactly once")
384}
385
386#[cfg(any(test, feature = "example-db"))]
387fn encode_edge_color(colors: &mut [Option<usize>], encoding: &EdgeEncoding, chosen_color: usize) {
388 match encoding {
389 EdgeEncoding::Direct { edge, allowed } => {
390 assert!(
391 allowed.contains(&chosen_color),
392 "chosen color {chosen_color} must belong to direct edge list {allowed:?}"
393 );
394 colors[*edge] = Some(chosen_color);
395 }
396 EdgeEncoding::TwoList {
397 left_outer,
398 middle,
399 right_outer,
400 first,
401 second,
402 } => {
403 assert!(
404 chosen_color == *first || chosen_color == *second,
405 "chosen color {chosen_color} must belong to two-list edge {{{first}, {second}}}"
406 );
407 let other = if chosen_color == *first {
408 *second
409 } else {
410 *first
411 };
412 colors[*left_outer] = Some(chosen_color);
413 colors[*right_outer] = Some(chosen_color);
414 colors[*middle] = Some(other);
415 }
416 }
417}
418
419#[cfg(any(test, feature = "example-db"))]
420fn edge_from_assignment(encoding: &EdgeEncoding, choose_first: bool) -> usize {
421 match encoding {
422 EdgeEncoding::Direct { allowed, .. } => allowed[usize::from(!choose_first)],
423 EdgeEncoding::TwoList { first, second, .. } => {
424 if choose_first {
425 *first
426 } else {
427 *second
428 }
429 }
430 }
431}
432
433fn bipartition(graph: &CoreGraph) -> Vec<bool> {
434 let mut side = vec![None; graph.blocked_colors.len()];
435 let mut adjacency = vec![Vec::new(); graph.blocked_colors.len()];
436 for &(u, v) in &graph.edges {
437 adjacency[u].push(v);
438 adjacency[v].push(u);
439 }
440
441 for start in 0..graph.blocked_colors.len() {
442 if side[start].is_some() {
443 continue;
444 }
445 side[start] = Some(false);
446 let mut queue = VecDeque::from([start]);
447 while let Some(vertex) = queue.pop_front() {
448 let current = side[vertex].expect("vertex has been assigned a side");
449 for &next in &adjacency[vertex] {
450 match side[next] {
451 Some(existing) => {
452 assert_ne!(existing, current, "core graph must remain bipartite");
453 }
454 None => {
455 side[next] = Some(!current);
456 queue.push_back(next);
457 }
458 }
459 }
460 }
461 }
462
463 side.into_iter()
464 .map(|entry| entry.expect("every vertex should receive a bipartition side"))
465 .collect()
466}
467
468fn build_layout(source: &KSatisfiability<K3>) -> ReductionLayout {
469 let normalized = normalize_formula(source);
470 let num_transformed_vars = normalized.transformed_to_original.len();
471 if num_transformed_vars == 0 && normalized.clauses.is_empty() {
472 return ReductionLayout {
473 source_num_vars: normalized.source_num_vars,
474 num_periods: 1,
475 craftsman_avail: Vec::new(),
476 task_avail: Vec::new(),
477 requirements: Vec::new(),
478 pure_assignments: normalized.pure_assignments,
479 transformed_to_original: normalized.transformed_to_original,
480 normalized_clauses: normalized.clauses,
481 variable_encodings: Vec::new(),
482 clause_encodings: Vec::new(),
483 edge_pairs: Vec::new(),
484 };
485 }
486
487 let num_periods = 4 * num_transformed_vars.max(1);
488 let all_colors: Vec<usize> = (0..num_periods).collect();
489
490 let mut occurrences_by_var: Vec<Vec<(usize, usize, bool)>> =
491 vec![Vec::new(); num_transformed_vars];
492 for (clause_idx, clause) in normalized.clauses.iter().enumerate() {
493 for (lit_idx, &literal) in clause.literals.iter().enumerate() {
494 occurrences_by_var[literal_var_index(literal)].push((clause_idx, lit_idx, literal > 0));
495 }
496 }
497
498 let mut clause_literal_colors: Vec<Vec<usize>> = normalized
499 .clauses
500 .iter()
501 .map(|clause| vec![usize::MAX; clause.literals.len()])
502 .collect();
503
504 let mut variable_colors = Vec::with_capacity(num_transformed_vars);
505 for (index, occurrences) in occurrences_by_var.iter().enumerate() {
506 let base = 4 * index;
507 let neg2 = base;
508 let neg1 = base + 1;
509 let pos2 = base + 2;
510 let pos1 = base + 3;
511
512 let mut positive_occurrences = Vec::new();
513 let mut negative_occurrences = Vec::new();
514 for &(clause_idx, lit_idx, is_positive) in occurrences {
515 if is_positive {
516 positive_occurrences.push((clause_idx, lit_idx));
517 } else {
518 negative_occurrences.push((clause_idx, lit_idx));
519 }
520 }
521
522 if let Some(&(clause_idx, lit_idx)) = positive_occurrences.first() {
523 clause_literal_colors[clause_idx][lit_idx] = pos1;
524 }
525 if let Some(&(clause_idx, lit_idx)) = positive_occurrences.get(1) {
526 clause_literal_colors[clause_idx][lit_idx] = pos2;
527 }
528 if let Some(&(clause_idx, lit_idx)) = negative_occurrences.first() {
529 clause_literal_colors[clause_idx][lit_idx] = neg1;
530 }
531 if let Some(&(clause_idx, lit_idx)) = negative_occurrences.get(1) {
532 clause_literal_colors[clause_idx][lit_idx] = neg2;
533 }
534
535 variable_colors.push((pos1, pos2, neg1, neg2));
536 }
537
538 let mut graph = CoreGraph::new();
539 let center = graph.add_vertex();
540 let mut variable_encodings = Vec::with_capacity(num_transformed_vars);
541
542 for &(pos1, pos2, neg1, neg2) in &variable_colors {
543 let a = graph.add_vertex();
544 let b = graph.add_vertex();
545 let c = graph.add_vertex();
546 let d = graph.add_vertex();
547 let e = graph.add_vertex();
548
549 let ab = add_two_list_edge(&mut graph, &all_colors, a, b, pos1, neg2);
550 let bc = add_two_list_edge(&mut graph, &all_colors, b, c, pos2, pos1);
551 let cd = add_two_list_edge(&mut graph, &all_colors, c, d, pos1, pos2);
552 let de = add_two_list_edge(&mut graph, &all_colors, d, e, pos2, neg1);
553 let vb = add_two_list_edge(&mut graph, &all_colors, center, b, neg2, pos2);
554 let vd = add_two_list_edge(&mut graph, &all_colors, center, d, neg1, pos1);
555
556 variable_encodings.push(VariableEncoding {
557 vb,
558 vd,
559 ab,
560 bc,
561 cd,
562 de,
563 neg2,
564 });
565 }
566
567 let mut clause_encodings = Vec::with_capacity(normalized.clauses.len());
568 for (clause_idx, _clause) in normalized.clauses.iter().enumerate() {
569 let clause_vertex = graph.add_vertex();
570 let colors = clause_literal_colors[clause_idx].clone();
571 debug_assert!(colors.iter().all(|&color| color != usize::MAX));
572
573 let edge = match colors.len() {
574 1 => add_direct_clause_edge(&mut graph, &all_colors, center, clause_vertex, colors),
575 2 => add_two_list_edge(
576 &mut graph,
577 &all_colors,
578 center,
579 clause_vertex,
580 colors[0],
581 colors[1],
582 ),
583 3 => add_direct_clause_edge(&mut graph, &all_colors, center, clause_vertex, colors),
584 len => panic!("expected clause size 1, 2, or 3 after normalization, got {len}"),
585 };
586
587 clause_encodings.push(ClauseEncoding { edge });
588 }
589
590 let side = bipartition(&graph);
591 let mut vertex_to_craftsman = vec![None; graph.blocked_colors.len()];
592 let mut vertex_to_task = vec![None; graph.blocked_colors.len()];
593
594 let mut num_craftsmen = 0usize;
595 let mut num_tasks = 0usize;
596 for (vertex, is_task_side) in side.iter().copied().enumerate() {
597 if is_task_side {
598 vertex_to_task[vertex] = Some(num_tasks);
599 num_tasks += 1;
600 } else {
601 vertex_to_craftsman[vertex] = Some(num_craftsmen);
602 num_craftsmen += 1;
603 }
604 }
605
606 let mut craftsman_avail = vec![vec![true; num_periods]; num_craftsmen];
607 let mut task_avail = vec![vec![true; num_periods]; num_tasks];
608 let mut requirements = vec![vec![0u64; num_tasks]; num_craftsmen];
609 let mut edge_pairs = vec![(usize::MAX, usize::MAX); graph.edges.len()];
610
611 for (edge_idx, &(u, v)) in graph.edges.iter().enumerate() {
612 let (craft, task) = if !side[u] {
613 (
614 vertex_to_craftsman[u].expect("left vertex has craftsman index"),
615 vertex_to_task[v].expect("right vertex has task index"),
616 )
617 } else {
618 (
619 vertex_to_craftsman[v].expect("left vertex has craftsman index"),
620 vertex_to_task[u].expect("right vertex has task index"),
621 )
622 };
623 requirements[craft][task] = 1;
624 edge_pairs[edge_idx] = (craft, task);
625 }
626
627 for (vertex, blocked_colors) in graph.blocked_colors.iter().enumerate() {
633 for &color in blocked_colors {
634 if side[vertex] {
635 let task = vertex_to_task[vertex].expect("right vertex has task index");
636 task_avail[task][color] = false;
637 } else {
638 let craft = vertex_to_craftsman[vertex].expect("left vertex has craftsman index");
639 craftsman_avail[craft][color] = false;
640 }
641 }
642 }
643
644 ReductionLayout {
645 source_num_vars: normalized.source_num_vars,
646 num_periods,
647 craftsman_avail,
648 task_avail,
649 requirements,
650 pure_assignments: normalized.pure_assignments,
651 transformed_to_original: normalized.transformed_to_original,
652 normalized_clauses: normalized.clauses,
653 variable_encodings,
654 clause_encodings,
655 edge_pairs,
656 }
657}
658
659impl Reduction3SATToTimetableDesign {
660 #[cfg(any(test, feature = "example-db"))]
661 fn construct_target_solution(&self, source_assignment: &[usize]) -> Option<Vec<usize>> {
662 if source_assignment.len() != self.layout.source_num_vars {
663 return None;
664 }
665 if self
666 .layout
667 .pure_assignments
668 .iter()
669 .enumerate()
670 .any(|(var, fixed)| fixed.is_some_and(|value| source_assignment[var] != value))
671 {
672 return None;
673 }
674
675 let transformed_assignment: Vec<usize> = self
676 .layout
677 .transformed_to_original
678 .iter()
679 .map(|&original| source_assignment[original])
680 .collect();
681
682 if self
683 .layout
684 .normalized_clauses
685 .iter()
686 .any(|clause| !evaluate_clause(clause, &transformed_assignment))
687 {
688 return None;
689 }
690
691 let mut colors = vec![None; self.layout.edge_pairs.len()];
692
693 for (transformed_var, encoding) in self.layout.variable_encodings.iter().enumerate() {
694 let choose_first = transformed_assignment[transformed_var] == 1;
695 for edge in [
696 &encoding.ab,
697 &encoding.bc,
698 &encoding.cd,
699 &encoding.de,
700 &encoding.vb,
701 &encoding.vd,
702 ] {
703 let chosen = edge_from_assignment(edge, choose_first);
704 encode_edge_color(&mut colors, edge, chosen);
705 }
706 }
707
708 for (clause, encoding) in self
709 .layout
710 .normalized_clauses
711 .iter()
712 .zip(self.layout.clause_encodings.iter())
713 {
714 let allowed = match &encoding.edge {
715 EdgeEncoding::Direct { allowed, .. } => allowed.clone(),
716 EdgeEncoding::TwoList { first, second, .. } => vec![*first, *second],
717 };
718 let chosen = choose_clause_edge_color(clause, &transformed_assignment, &allowed)?;
719 encode_edge_color(&mut colors, &encoding.edge, chosen);
720 }
721
722 if colors.iter().any(Option::is_none) {
723 return None;
724 }
725
726 let num_tasks = self.target.num_tasks();
727 let num_periods = self.target.num_periods();
728 let mut config = vec![0usize; self.target.dims().len()];
729
730 for (edge_idx, color) in colors.into_iter().enumerate() {
731 let (craft, task) = self.layout.edge_pairs[edge_idx];
732 let color = color.expect("all core edges should be colored");
733 config[((craft * num_tasks) + task) * num_periods + color] = 1;
734 }
735
736 Some(config)
737 }
738}
739
740impl ReductionResult for Reduction3SATToTimetableDesign {
741 type Source = KSatisfiability<K3>;
742 type Target = TimetableDesign;
743
744 fn target_problem(&self) -> &Self::Target {
745 &self.target
746 }
747
748 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
749 let num_tasks = self.target.num_tasks();
750 let num_periods = self.target.num_periods();
751
752 let mut transformed_assignment = vec![0usize; self.layout.transformed_to_original.len()];
753 for (index, encoding) in self.layout.variable_encodings.iter().enumerate() {
754 let vb_pair = match &encoding.vb {
755 EdgeEncoding::Direct { edge, .. } => self.layout.edge_pairs[*edge],
756 EdgeEncoding::TwoList { left_outer, .. } => self.layout.edge_pairs[*left_outer],
757 };
758 let vb_color = core_edge_color(target_solution, vb_pair, num_tasks, num_periods);
759 transformed_assignment[index] = usize::from(vb_color == encoding.neg2);
760 }
761
762 let mut source_assignment = vec![0usize; self.layout.source_num_vars];
763 for (var, fixed) in self.layout.pure_assignments.iter().copied().enumerate() {
764 if let Some(value) = fixed {
765 source_assignment[var] = value;
766 }
767 }
768
769 let mut seen_transformed = vec![false; self.layout.source_num_vars];
770 for (value, &original_var) in transformed_assignment
771 .iter()
772 .zip(self.layout.transformed_to_original.iter())
773 {
774 if !seen_transformed[original_var] {
775 source_assignment[original_var] = *value;
776 seen_transformed[original_var] = true;
777 }
778 }
779
780 source_assignment
781 }
782}
783
784#[reduction(overhead = {
785 num_periods = "4 * num_literals",
786 num_craftsmen = "24 * num_literals + 1",
787 num_tasks = "24 * num_literals + 1",
788})]
789impl ReduceTo<TimetableDesign> for KSatisfiability<K3> {
790 type Result = Reduction3SATToTimetableDesign;
791
792 fn reduce_to(&self) -> Self::Result {
793 let layout = build_layout(self);
794 let target = TimetableDesign::new(
795 layout.num_periods,
796 layout.craftsman_avail.len(),
797 layout.task_avail.len(),
798 layout.craftsman_avail.clone(),
799 layout.task_avail.clone(),
800 layout.requirements.clone(),
801 );
802
803 Reduction3SATToTimetableDesign { target, layout }
804 }
805}
806
807#[cfg(any(test, feature = "example-db"))]
808#[allow(dead_code)]
809pub(super) fn construct_timetable_from_assignment(
810 target: &TimetableDesign,
811 assignment: &[usize],
812 source: &KSatisfiability<K3>,
813) -> Option<Vec<usize>> {
814 let reduction = ReduceTo::<TimetableDesign>::reduce_to(source);
815 if reduction.target_problem().num_periods() != target.num_periods()
816 || reduction.target_problem().num_craftsmen() != target.num_craftsmen()
817 || reduction.target_problem().num_tasks() != target.num_tasks()
818 || reduction.target_problem().craftsman_avail() != target.craftsman_avail()
819 || reduction.target_problem().task_avail() != target.task_avail()
820 || reduction.target_problem().requirements() != target.requirements()
821 {
822 return None;
823 }
824 reduction.construct_target_solution(assignment)
825}
826
827#[cfg(feature = "example-db")]
828pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
829 use crate::export::SolutionPair;
830 use crate::models::formula::CNFClause;
831
832 vec![crate::example_db::specs::RuleExampleSpec {
833 id: "ksatisfiability_to_timetabledesign",
834 build: || {
835 let source = KSatisfiability::<K3>::new(
836 3,
837 vec![
838 CNFClause::new(vec![1, 2, 3]),
839 CNFClause::new(vec![-1, -2, -3]),
840 ],
841 );
842 let reduction = ReduceTo::<TimetableDesign>::reduce_to(&source);
843 let source_config = vec![1, 0, 0];
844 let target_config = reduction
845 .construct_target_solution(&source_config)
846 .expect("canonical satisfying assignment should lift to timetable");
847
848 crate::example_db::specs::rule_example_with_witness::<_, TimetableDesign>(
849 source,
850 SolutionPair {
851 source_config,
852 target_config,
853 },
854 )
855 },
856 }]
857}
858
859#[cfg(test)]
860#[path = "../unit_tests/rules/ksatisfiability_timetabledesign.rs"]
861mod tests;