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