1use std::collections::{HashMap, VecDeque};
25use std::time::Instant;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
33pub struct CspVarId(pub usize);
34
35#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct Domain {
42 pub values: Vec<i64>,
44}
45
46impl Domain {
47 pub fn new(mut values: Vec<i64>) -> Self {
49 values.sort_unstable();
50 values.dedup();
51 Self { values }
52 }
53
54 #[inline]
56 pub fn is_empty(&self) -> bool {
57 self.values.is_empty()
58 }
59
60 pub fn contains(&self, v: i64) -> bool {
62 self.values.binary_search(&v).is_ok()
63 }
64
65 pub fn remove(&mut self, v: i64) -> bool {
67 if let Ok(idx) = self.values.binary_search(&v) {
68 self.values.remove(idx);
69 true
70 } else {
71 false
72 }
73 }
74
75 #[inline]
77 pub fn len(&self) -> usize {
78 self.values.len()
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
88pub enum Constraint {
89 AllDifferent(Vec<CspVarId>),
91 Equal(CspVarId, CspVarId),
93 NotEqual(CspVarId, CspVarId),
95 LessThan(CspVarId, CspVarId),
97 LessEqual(CspVarId, CspVarId),
99 Sum {
101 vars: Vec<CspVarId>,
103 target: i64,
105 },
106 InDomain {
108 var: CspVarId,
110 allowed: Vec<i64>,
112 },
113}
114
115impl Constraint {
116 pub fn variables(&self) -> Vec<CspVarId> {
118 match self {
119 Constraint::AllDifferent(vars) => vars.clone(),
120 Constraint::Equal(a, b)
121 | Constraint::NotEqual(a, b)
122 | Constraint::LessThan(a, b)
123 | Constraint::LessEqual(a, b) => vec![*a, *b],
124 Constraint::Sum { vars, .. } => vars.clone(),
125 Constraint::InDomain { var, .. } => vec![*var],
126 }
127 }
128
129 pub fn involves(&self, var: CspVarId) -> bool {
131 self.variables().contains(&var)
132 }
133
134 fn involves_pair(&self, xi: CspVarId, xj: CspVarId) -> bool {
136 let vars = self.variables();
137 vars.contains(&xi) && vars.contains(&xj)
138 }
139}
140
141#[derive(Debug, Clone, PartialEq, Eq)]
147pub struct Assignment {
148 pub values: HashMap<usize, i64>,
150}
151
152impl Assignment {
153 pub fn new() -> Self {
155 Self {
156 values: HashMap::new(),
157 }
158 }
159
160 #[inline]
162 pub fn is_complete(&self, num_vars: usize) -> bool {
163 self.values.len() == num_vars
164 }
165
166 #[inline]
168 pub fn get(&self, var: CspVarId) -> Option<i64> {
169 self.values.get(&var.0).copied()
170 }
171
172 #[inline]
174 pub fn set(&mut self, var: CspVarId, value: i64) {
175 self.values.insert(var.0, value);
176 }
177
178 #[inline]
180 pub fn unset(&mut self, var: CspVarId) {
181 self.values.remove(&var.0);
182 }
183
184 pub fn iter(&self) -> impl Iterator<Item = (CspVarId, i64)> + '_ {
186 self.values.iter().map(|(&id, &v)| (CspVarId(id), v))
187 }
188}
189
190impl Default for Assignment {
191 fn default() -> Self {
192 Self::new()
193 }
194}
195
196#[derive(Debug, Clone)]
202pub struct CspVariable {
203 pub id: CspVarId,
205 pub name: String,
207 pub domain: Domain,
209}
210
211#[derive(Debug, Clone, PartialEq, Eq)]
217pub struct SolverConfig {
218 pub max_solutions: usize,
220 pub use_ac3: bool,
222 pub use_mrv: bool,
225 pub use_lcv: bool,
228 pub max_backtracks: usize,
230}
231
232impl Default for SolverConfig {
233 fn default() -> Self {
234 Self {
235 max_solutions: 1,
236 use_ac3: true,
237 use_mrv: true,
238 use_lcv: false,
239 max_backtracks: 100_000,
240 }
241 }
242}
243
244#[derive(Debug, Clone)]
250pub struct SolverResult {
251 pub solutions: Vec<Assignment>,
253 pub backtracks: u64,
255 pub constraint_checks: u64,
257 pub time_ms: u64,
259}
260
261impl SolverResult {
262 fn new() -> Self {
263 Self {
264 solutions: Vec::new(),
265 backtracks: 0,
266 constraint_checks: 0,
267 time_ms: 0,
268 }
269 }
270}
271
272#[derive(Debug, Clone, PartialEq, Eq)]
278pub enum CspError {
279 VariableNotFound(usize),
281 InvalidConstraint(String),
283 UnsatisfiableAfterAC3,
285}
286
287impl std::fmt::Display for CspError {
288 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
289 match self {
290 CspError::VariableNotFound(id) => write!(f, "Variable not found: {}", id),
291 CspError::InvalidConstraint(msg) => write!(f, "Invalid constraint: {}", msg),
292 CspError::UnsatisfiableAfterAC3 => {
293 write!(f, "Problem is unsatisfiable after AC-3 preprocessing")
294 }
295 }
296 }
297}
298
299impl std::error::Error for CspError {}
300
301#[derive(Debug, Clone, PartialEq, Eq)]
307pub struct CspStats {
308 pub num_variables: usize,
310 pub num_constraints: usize,
312 pub total_domain_size: usize,
314}
315
316pub struct ConstraintSolver {
323 pub variables: Vec<CspVariable>,
325 pub constraints: Vec<Constraint>,
327 pub config: SolverConfig,
329}
330
331impl ConstraintSolver {
332 pub fn new(config: SolverConfig) -> Self {
338 Self {
339 variables: Vec::new(),
340 constraints: Vec::new(),
341 config,
342 }
343 }
344
345 pub fn add_variable(&mut self, name: String, domain: Vec<i64>) -> CspVarId {
349 let id = CspVarId(self.variables.len());
350 self.variables.push(CspVariable {
351 id,
352 name,
353 domain: Domain::new(domain),
354 });
355 id
356 }
357
358 pub fn add_constraint(&mut self, c: Constraint) {
360 self.constraints.push(c);
361 }
362
363 pub fn stats(&self) -> CspStats {
369 CspStats {
370 num_variables: self.variables.len(),
371 num_constraints: self.constraints.len(),
372 total_domain_size: self.variables.iter().map(|v| v.domain.len()).sum(),
373 }
374 }
375
376 pub fn is_consistent(&self, assignment: &Assignment, var: CspVarId, value: i64) -> bool {
384 self.constraints
385 .iter()
386 .all(|c| self.check_constraint(c, assignment, var, value))
387 }
388
389 pub fn check_constraint(
395 &self,
396 c: &Constraint,
397 assignment: &Assignment,
398 var: CspVarId,
399 value: i64,
400 ) -> bool {
401 let get = |v: CspVarId| -> Option<i64> {
404 if v == var {
405 Some(value)
406 } else {
407 assignment.get(v)
408 }
409 };
410
411 match c {
412 Constraint::Equal(a, b) => match (get(*a), get(*b)) {
413 (Some(va), Some(vb)) => va == vb,
414 _ => true,
415 },
416 Constraint::NotEqual(a, b) => match (get(*a), get(*b)) {
417 (Some(va), Some(vb)) => va != vb,
418 _ => true,
419 },
420 Constraint::LessThan(a, b) => match (get(*a), get(*b)) {
421 (Some(va), Some(vb)) => va < vb,
422 _ => true,
423 },
424 Constraint::LessEqual(a, b) => match (get(*a), get(*b)) {
425 (Some(va), Some(vb)) => va <= vb,
426 _ => true,
427 },
428 Constraint::AllDifferent(vars) => {
429 let mut seen: Vec<i64> = Vec::with_capacity(vars.len());
431 for &v in vars {
432 if let Some(val) = get(v) {
433 if seen.contains(&val) {
434 return false;
435 }
436 seen.push(val);
437 }
438 }
439 true
440 }
441 Constraint::Sum { vars, target } => {
442 let mut total: i64 = 0;
444 let mut all_assigned = true;
445 let mut partial_sum: i64 = 0;
446 let mut partial_count = 0;
447 for &v in vars {
448 match get(v) {
449 Some(val) => {
450 total += val;
451 partial_sum += val;
452 partial_count += 1;
453 }
454 None => {
455 all_assigned = false;
456 }
457 }
458 }
459 let _ = (total, partial_sum, partial_count);
460 if all_assigned {
461 let mut s: i64 = 0;
463 for &v in vars {
464 s += get(v).unwrap_or(0);
465 }
466 s == *target
467 } else {
468 let partial: i64 = vars.iter().filter_map(|&v| get(v)).sum();
471 partial <= *target
472 }
473 }
474 Constraint::InDomain { var: dvar, allowed } => {
475 if let Some(val) = get(*dvar) {
476 allowed.contains(&val)
477 } else {
478 true
479 }
480 }
481 }
482 }
483
484 pub fn ac3(&self, domains: &mut [Domain]) -> bool {
494 let mut worklist: VecDeque<(CspVarId, CspVarId)> = VecDeque::new();
496
497 for c in &self.constraints {
498 match c {
499 Constraint::Equal(a, b)
500 | Constraint::NotEqual(a, b)
501 | Constraint::LessThan(a, b)
502 | Constraint::LessEqual(a, b) => {
503 worklist.push_back((*a, *b));
504 worklist.push_back((*b, *a));
505 }
506 Constraint::AllDifferent(vars) => {
507 for i in 0..vars.len() {
508 for j in 0..vars.len() {
509 if i != j {
510 worklist.push_back((vars[i], vars[j]));
511 }
512 }
513 }
514 }
515 Constraint::InDomain { var, allowed } => {
517 let idx = var.0;
518 if idx < domains.len() {
519 domains[idx].values.retain(|v| allowed.contains(v));
520 if domains[idx].is_empty() {
521 return false;
522 }
523 }
524 }
525 Constraint::Sum { .. } => {}
527 }
528 }
529
530 while let Some((xi, xj)) = worklist.pop_front() {
531 if self.revise(domains, xi, xj) {
532 if domains.get(xi.0).is_none_or(Domain::is_empty) {
533 return false;
534 }
535 let neighbours = self.neighbours_of(xi);
537 for xk in neighbours {
538 if xk != xj {
539 worklist.push_back((xk, xi));
540 }
541 }
542 }
543 }
544
545 true
546 }
547
548 pub fn revise(&self, domains: &mut [Domain], xi: CspVarId, xj: CspVarId) -> bool {
552 if xi.0 >= domains.len() || xj.0 >= domains.len() {
553 return false;
554 }
555
556 let xj_vals: Vec<i64> = domains[xj.0].values.clone();
558 let xi_vals: Vec<i64> = domains[xi.0].values.clone();
559
560 let relevant: Vec<&Constraint> = self
562 .constraints
563 .iter()
564 .filter(|c| c.involves_pair(xi, xj))
565 .collect();
566
567 let mut to_remove: Vec<i64> = Vec::new();
568 for &vxi in &xi_vals {
569 let has_support = xj_vals.iter().any(|&vyj| {
570 relevant.iter().all(|c| {
571 let mut tmp = Assignment::new();
572 tmp.set(xi, vxi);
573 tmp.set(xj, vyj);
574 self.check_constraint(c, &tmp, CspVarId(usize::MAX), 0)
577 })
578 });
579 if !has_support {
580 to_remove.push(vxi);
581 }
582 }
583
584 if to_remove.is_empty() {
585 return false;
586 }
587 for v in &to_remove {
588 domains[xi.0].remove(*v);
589 }
590 true
591 }
592
593 fn neighbours_of(&self, var: CspVarId) -> Vec<CspVarId> {
595 let mut result: Vec<CspVarId> = Vec::new();
596 for c in &self.constraints {
597 match c {
598 Constraint::Equal(a, b)
599 | Constraint::NotEqual(a, b)
600 | Constraint::LessThan(a, b)
601 | Constraint::LessEqual(a, b) => {
602 if *a == var && !result.contains(b) {
603 result.push(*b);
604 } else if *b == var && !result.contains(a) {
605 result.push(*a);
606 }
607 }
608 Constraint::AllDifferent(vars) => {
609 if vars.contains(&var) {
610 for &v in vars {
611 if v != var && !result.contains(&v) {
612 result.push(v);
613 }
614 }
615 }
616 }
617 Constraint::Sum { vars, .. } => {
618 if vars.contains(&var) {
619 for &v in vars {
620 if v != var && !result.contains(&v) {
621 result.push(v);
622 }
623 }
624 }
625 }
626 Constraint::InDomain { .. } => {}
627 }
628 }
629 result
630 }
631
632 pub fn select_unassigned_variable(
642 &self,
643 assignment: &Assignment,
644 domains: &[Domain],
645 ) -> Option<CspVarId> {
646 let unassigned: Vec<CspVarId> = self
647 .variables
648 .iter()
649 .map(|v| v.id)
650 .filter(|id| assignment.get(*id).is_none())
651 .collect();
652
653 if unassigned.is_empty() {
654 return None;
655 }
656
657 if self.config.use_mrv {
658 unassigned.into_iter().min_by_key(|id| {
659 let size = domains.get(id.0).map_or(0, Domain::len);
660 (size, id.0)
662 })
663 } else {
664 unassigned.into_iter().next()
665 }
666 }
667
668 pub fn order_domain_values(
675 &self,
676 var: CspVarId,
677 _assignment: &Assignment,
678 domains: &[Domain],
679 ) -> Vec<i64> {
680 domains
681 .get(var.0)
682 .map_or_else(Vec::new, |d| d.values.clone())
683 }
684
685 pub fn backtrack(
694 &self,
695 assignment: &mut Assignment,
696 domains: &mut Vec<Domain>,
697 result: &mut SolverResult,
698 ) -> bool {
699 if self.config.max_backtracks > 0 && result.backtracks >= self.config.max_backtracks as u64
701 {
702 return true;
703 }
704
705 if assignment.is_complete(self.variables.len()) {
707 result.solutions.push(assignment.clone());
708 return result.solutions.len() >= self.config.max_solutions;
709 }
710
711 let var = match self.select_unassigned_variable(assignment, domains) {
713 Some(v) => v,
714 None => return false,
715 };
716
717 let ordered_values = self.order_domain_values(var, assignment, domains);
718
719 for value in ordered_values {
720 result.constraint_checks += 1;
721 if self.is_consistent(assignment, var, value) {
722 assignment.set(var, value);
724
725 let domains_backup: Vec<Domain> = domains.clone();
727 let mut fc_ok = true;
728 for neighbour in self.neighbours_of(var) {
729 if assignment.get(neighbour).is_some() {
730 continue;
731 }
732 let orig: Vec<i64> = domains[neighbour.0].values.clone();
733 let pruned: Vec<i64> = orig
734 .into_iter()
735 .filter(|&nv| self.is_consistent(assignment, neighbour, nv))
736 .collect();
737 domains[neighbour.0].values = pruned;
738 if domains[neighbour.0].is_empty() {
739 fc_ok = false;
740 break;
741 }
742 }
743
744 if fc_ok && self.backtrack(assignment, domains, result) {
745 return true;
746 }
747
748 *domains = domains_backup;
750 assignment.unset(var);
751 result.backtracks += 1;
752 }
753 }
754
755 false
756 }
757
758 pub fn solve(&mut self) -> SolverResult {
765 let start = Instant::now();
766 let mut result = SolverResult::new();
767
768 let mut domains: Vec<Domain> = self.variables.iter().map(|v| v.domain.clone()).collect();
770
771 if self.config.use_ac3 && !self.ac3(&mut domains) {
773 result.time_ms = start.elapsed().as_millis() as u64;
774 return result; }
776
777 if domains.iter().any(Domain::is_empty) {
779 result.time_ms = start.elapsed().as_millis() as u64;
780 return result;
781 }
782
783 let mut assignment = Assignment::new();
784 self.backtrack(&mut assignment, &mut domains, &mut result);
785
786 result.time_ms = start.elapsed().as_millis() as u64;
787 result
788 }
789}
790
791#[cfg(test)]
796mod tests {
797 use crate::constraint_solver::{
798 Assignment, Constraint, ConstraintSolver, CspVarId, Domain, SolverConfig, SolverResult,
799 };
800
801 fn default_solver() -> ConstraintSolver {
802 ConstraintSolver::new(SolverConfig::default())
803 }
804
805 #[test]
810 fn test_domain_new_sorts_and_deduplicates() {
811 let d = Domain::new(vec![3, 1, 2, 1, 3]);
812 assert_eq!(d.values, vec![1, 2, 3]);
813 }
814
815 #[test]
816 fn test_domain_is_empty_when_no_values() {
817 assert!(Domain::new(vec![]).is_empty());
818 assert!(!Domain::new(vec![1]).is_empty());
819 }
820
821 #[test]
822 fn test_domain_contains() {
823 let d = Domain::new(vec![10, 20, 30]);
824 assert!(d.contains(10));
825 assert!(d.contains(20));
826 assert!(!d.contains(5));
827 assert!(!d.contains(99));
828 }
829
830 #[test]
831 fn test_domain_remove_present_value() {
832 let mut d = Domain::new(vec![1, 2, 3]);
833 let removed = d.remove(2);
834 assert!(removed);
835 assert_eq!(d.values, vec![1, 3]);
836 }
837
838 #[test]
839 fn test_domain_remove_absent_value_is_noop() {
840 let mut d = Domain::new(vec![1, 2, 3]);
841 let removed = d.remove(99);
842 assert!(!removed);
843 assert_eq!(d.values, vec![1, 2, 3]);
844 }
845
846 #[test]
847 fn test_domain_len() {
848 assert_eq!(Domain::new(vec![1, 2, 3]).len(), 3);
849 assert_eq!(Domain::new(vec![]).len(), 0);
850 }
851
852 #[test]
857 fn test_assignment_set_and_get() {
858 let mut a = Assignment::new();
859 let v = CspVarId(0);
860 assert_eq!(a.get(v), None);
861 a.set(v, 42);
862 assert_eq!(a.get(v), Some(42));
863 }
864
865 #[test]
866 fn test_assignment_unset() {
867 let mut a = Assignment::new();
868 let v = CspVarId(0);
869 a.set(v, 7);
870 a.unset(v);
871 assert_eq!(a.get(v), None);
872 }
873
874 #[test]
875 fn test_assignment_is_complete() {
876 let mut a = Assignment::new();
877 assert!(!a.is_complete(2));
878 a.set(CspVarId(0), 1);
879 a.set(CspVarId(1), 2);
880 assert!(a.is_complete(2));
881 }
882
883 #[test]
888 fn test_add_variable_returns_sequential_ids() {
889 let mut solver = default_solver();
890 let x = solver.add_variable("x".to_string(), vec![1, 2]);
891 let y = solver.add_variable("y".to_string(), vec![3, 4]);
892 assert_eq!(x, CspVarId(0));
893 assert_eq!(y, CspVarId(1));
894 }
895
896 #[test]
897 fn test_stats() {
898 let mut solver = default_solver();
899 solver.add_variable("a".to_string(), vec![1, 2, 3]);
900 solver.add_variable("b".to_string(), vec![4, 5]);
901 let s = solver.stats();
902 assert_eq!(s.num_variables, 2);
903 assert_eq!(s.total_domain_size, 5);
904 assert_eq!(s.num_constraints, 0);
905 }
906
907 #[test]
912 fn test_no_constraints_always_satisfiable() {
913 let mut solver = default_solver();
914 let _x = solver.add_variable("x".to_string(), vec![42]);
915 let result = solver.solve();
916 assert_eq!(result.solutions.len(), 1);
917 assert_eq!(result.solutions[0].get(CspVarId(0)), Some(42));
918 }
919
920 #[test]
921 fn test_not_equal_two_vars_satisfiable() {
922 let mut solver = default_solver();
923 let x = solver.add_variable("x".to_string(), vec![1, 2]);
924 let y = solver.add_variable("y".to_string(), vec![1, 2]);
925 solver.add_constraint(Constraint::NotEqual(x, y));
926 let result = solver.solve();
927 assert_eq!(result.solutions.len(), 1);
928 let sol = &result.solutions[0];
929 assert_ne!(sol.get(x), sol.get(y));
930 }
931
932 #[test]
933 fn test_equal_constraint() {
934 let mut solver = default_solver();
935 let x = solver.add_variable("x".to_string(), vec![1, 2, 3]);
936 let y = solver.add_variable("y".to_string(), vec![2, 3, 4]);
937 solver.add_constraint(Constraint::Equal(x, y));
938 let result = solver.solve();
939 assert!(!result.solutions.is_empty());
940 let sol = &result.solutions[0];
941 assert_eq!(sol.get(x), sol.get(y));
942 }
943
944 #[test]
945 fn test_less_than_constraint() {
946 let mut solver = default_solver();
947 let a = solver.add_variable("a".to_string(), vec![1, 2, 3]);
948 let b = solver.add_variable("b".to_string(), vec![1, 2, 3]);
949 solver.add_constraint(Constraint::LessThan(a, b));
950 let result = solver.solve();
951 assert!(!result.solutions.is_empty());
952 let sol = &result.solutions[0];
953 assert!(
954 sol.get(a).expect("test: should succeed") < sol.get(b).expect("test: should succeed")
955 );
956 }
957
958 #[test]
959 fn test_less_equal_constraint() {
960 let mut solver = default_solver();
961 let a = solver.add_variable("a".to_string(), vec![5]);
962 let b = solver.add_variable("b".to_string(), vec![5]);
963 solver.add_constraint(Constraint::LessEqual(a, b));
964 let result = solver.solve();
965 assert_eq!(result.solutions.len(), 1);
966 let sol = &result.solutions[0];
967 assert!(
968 sol.get(a).expect("test: should succeed") <= sol.get(b).expect("test: should succeed")
969 );
970 }
971
972 #[test]
973 fn test_all_different_three_vars() {
974 let mut solver = default_solver();
975 let x = solver.add_variable("x".to_string(), vec![1, 2, 3]);
976 let y = solver.add_variable("y".to_string(), vec![1, 2, 3]);
977 let z = solver.add_variable("z".to_string(), vec![1, 2, 3]);
978 solver.add_constraint(Constraint::AllDifferent(vec![x, y, z]));
979 let result = solver.solve();
980 assert_eq!(result.solutions.len(), 1);
981 let sol = &result.solutions[0];
982 assert_ne!(sol.get(x), sol.get(y));
983 assert_ne!(sol.get(x), sol.get(z));
984 assert_ne!(sol.get(y), sol.get(z));
985 }
986
987 #[test]
988 fn test_in_domain_constraint() {
989 let mut solver = default_solver();
990 let x = solver.add_variable("x".to_string(), vec![1, 2, 3, 4, 5]);
991 solver.add_constraint(Constraint::InDomain {
992 var: x,
993 allowed: vec![2, 4],
994 });
995 let result = solver.solve();
996 assert_eq!(result.solutions.len(), 1);
997 let val = result.solutions[0].get(x).unwrap_or(-1);
998 assert!(val == 2 || val == 4);
999 }
1000
1001 #[test]
1002 fn test_sum_constraint_exact() {
1003 let mut solver = ConstraintSolver::new(SolverConfig {
1004 use_ac3: false, ..SolverConfig::default()
1006 });
1007 let a = solver.add_variable("a".to_string(), vec![1, 2, 3]);
1008 let b = solver.add_variable("b".to_string(), vec![1, 2, 3]);
1009 solver.add_constraint(Constraint::Sum {
1010 vars: vec![a, b],
1011 target: 4,
1012 });
1013 let result = solver.solve();
1014 assert!(!result.solutions.is_empty());
1015 let sol = &result.solutions[0];
1016 assert_eq!(sol.get(a).unwrap_or(0) + sol.get(b).unwrap_or(0), 4);
1017 }
1018
1019 #[test]
1024 fn test_empty_domain_unsatisfiable() {
1025 let mut solver = default_solver();
1026 solver.add_variable("x".to_string(), vec![]);
1027 let result = solver.solve();
1028 assert!(result.solutions.is_empty());
1029 }
1030
1031 #[test]
1032 fn test_not_equal_single_value_unsatisfiable() {
1033 let mut solver = default_solver();
1034 let x = solver.add_variable("x".to_string(), vec![5]);
1035 let y = solver.add_variable("y".to_string(), vec![5]);
1036 solver.add_constraint(Constraint::NotEqual(x, y));
1037 let result = solver.solve();
1038 assert!(result.solutions.is_empty());
1039 }
1040
1041 #[test]
1042 fn test_all_different_too_few_values_unsatisfiable() {
1043 let mut solver = default_solver();
1044 let x = solver.add_variable("x".to_string(), vec![1, 2]);
1045 let y = solver.add_variable("y".to_string(), vec![1, 2]);
1046 let z = solver.add_variable("z".to_string(), vec![1, 2]);
1047 solver.add_constraint(Constraint::AllDifferent(vec![x, y, z]));
1048 let result = solver.solve();
1049 assert!(result.solutions.is_empty());
1050 }
1051
1052 #[test]
1053 fn test_less_than_equal_values_unsatisfiable() {
1054 let mut solver = default_solver();
1055 let a = solver.add_variable("a".to_string(), vec![5]);
1056 let b = solver.add_variable("b".to_string(), vec![5]);
1057 solver.add_constraint(Constraint::LessThan(a, b));
1058 let result = solver.solve();
1059 assert!(result.solutions.is_empty());
1060 }
1061
1062 #[test]
1063 fn test_equal_disjoint_domains_unsatisfiable() {
1064 let mut solver = default_solver();
1065 let x = solver.add_variable("x".to_string(), vec![1, 2]);
1066 let y = solver.add_variable("y".to_string(), vec![3, 4]);
1067 solver.add_constraint(Constraint::Equal(x, y));
1068 let result = solver.solve();
1069 assert!(result.solutions.is_empty());
1070 }
1071
1072 #[test]
1073 fn test_in_domain_no_overlap_unsatisfiable() {
1074 let mut solver = default_solver();
1075 let x = solver.add_variable("x".to_string(), vec![1, 2, 3]);
1076 solver.add_constraint(Constraint::InDomain {
1077 var: x,
1078 allowed: vec![7, 8, 9],
1079 });
1080 let result = solver.solve();
1081 assert!(result.solutions.is_empty());
1082 }
1083
1084 #[test]
1089 fn test_find_all_solutions() {
1090 let mut solver = ConstraintSolver::new(SolverConfig {
1091 max_solutions: 100,
1092 use_ac3: false,
1093 ..SolverConfig::default()
1094 });
1095 let x = solver.add_variable("x".to_string(), vec![1, 2, 3]);
1096 let y = solver.add_variable("y".to_string(), vec![1, 2, 3]);
1097 solver.add_constraint(Constraint::NotEqual(x, y));
1098 let result = solver.solve();
1099 assert_eq!(result.solutions.len(), 6);
1101 for sol in &result.solutions {
1102 assert_ne!(sol.get(x), sol.get(y));
1103 }
1104 }
1105
1106 #[test]
1111 fn test_ac3_prunes_domain_for_not_equal() {
1112 let mut solver = ConstraintSolver::new(SolverConfig {
1113 use_ac3: true,
1114 ..SolverConfig::default()
1115 });
1116 let x = solver.add_variable("x".to_string(), vec![5]);
1118 let y = solver.add_variable("y".to_string(), vec![5, 6]);
1119 solver.add_constraint(Constraint::NotEqual(x, y));
1120
1121 let mut domains: Vec<Domain> = solver.variables.iter().map(|v| v.domain.clone()).collect();
1122 let consistent = solver.ac3(&mut domains);
1123 assert!(consistent);
1124 assert_eq!(domains[y.0].values, vec![6]);
1125 }
1126
1127 #[test]
1128 fn test_ac3_detects_unsatisfiability() {
1129 let mut solver = ConstraintSolver::new(SolverConfig {
1130 use_ac3: true,
1131 ..SolverConfig::default()
1132 });
1133 let x = solver.add_variable("x".to_string(), vec![5]);
1134 let y = solver.add_variable("y".to_string(), vec![5]);
1135 solver.add_constraint(Constraint::NotEqual(x, y));
1136
1137 let mut domains: Vec<Domain> = solver.variables.iter().map(|v| v.domain.clone()).collect();
1138 let consistent = solver.ac3(&mut domains);
1139 assert!(!consistent);
1140 }
1141
1142 #[test]
1143 fn test_ac3_prunes_less_than() {
1144 let mut solver = ConstraintSolver::new(SolverConfig {
1145 use_ac3: true,
1146 ..SolverConfig::default()
1147 });
1148 let a = solver.add_variable("a".to_string(), vec![3, 4, 5]);
1153 let b = solver.add_variable("b".to_string(), vec![1, 2, 3]);
1154 solver.add_constraint(Constraint::LessThan(a, b));
1155 let result = solver.solve();
1156 assert!(result.solutions.is_empty());
1158 }
1159
1160 #[test]
1161 fn test_ac3_in_domain_pruning() {
1162 let mut solver = default_solver();
1163 let x = solver.add_variable("x".to_string(), vec![1, 2, 3, 4, 5]);
1164 solver.add_constraint(Constraint::InDomain {
1165 var: x,
1166 allowed: vec![3],
1167 });
1168 let mut domains: Vec<Domain> = solver.variables.iter().map(|v| v.domain.clone()).collect();
1169 let ok = solver.ac3(&mut domains);
1170 assert!(ok);
1171 assert_eq!(domains[x.0].values, vec![3]);
1172 }
1173
1174 #[test]
1179 fn test_mrv_selects_smallest_domain() {
1180 let mut solver = ConstraintSolver::new(SolverConfig {
1181 use_mrv: true,
1182 ..SolverConfig::default()
1183 });
1184 let x = solver.add_variable("x".to_string(), vec![1, 2, 3]);
1185 let y = solver.add_variable("y".to_string(), vec![1]);
1186 let domains: Vec<Domain> = solver.variables.iter().map(|v| v.domain.clone()).collect();
1187 let assignment = Assignment::new();
1188 let chosen = solver
1189 .select_unassigned_variable(&assignment, &domains)
1190 .expect("test: should succeed");
1191 assert_eq!(chosen, y);
1193 let _ = x;
1194 }
1195
1196 #[test]
1197 fn test_no_mrv_selects_first() {
1198 let mut solver = ConstraintSolver::new(SolverConfig {
1199 use_mrv: false,
1200 ..SolverConfig::default()
1201 });
1202 let x = solver.add_variable("x".to_string(), vec![1, 2, 3]);
1203 let _y = solver.add_variable("y".to_string(), vec![1]);
1204 let domains: Vec<Domain> = solver.variables.iter().map(|v| v.domain.clone()).collect();
1205 let assignment = Assignment::new();
1206 let chosen = solver
1207 .select_unassigned_variable(&assignment, &domains)
1208 .expect("test: should succeed");
1209 assert_eq!(chosen, x);
1211 }
1212
1213 #[test]
1214 fn test_order_domain_values_returns_sorted() {
1215 let mut solver = default_solver();
1216 let x = solver.add_variable("x".to_string(), vec![5, 3, 1, 4, 2]);
1217 let domains: Vec<Domain> = solver.variables.iter().map(|v| v.domain.clone()).collect();
1218 let vals = solver.order_domain_values(x, &Assignment::new(), &domains);
1219 assert_eq!(vals, vec![1, 2, 3, 4, 5]);
1220 }
1221
1222 #[test]
1227 fn test_chained_less_than() {
1228 let mut solver = default_solver();
1229 let a = solver.add_variable("a".to_string(), vec![1, 2, 3]);
1230 let b = solver.add_variable("b".to_string(), vec![1, 2, 3]);
1231 let c = solver.add_variable("c".to_string(), vec![1, 2, 3]);
1232 solver.add_constraint(Constraint::LessThan(a, b));
1233 solver.add_constraint(Constraint::LessThan(b, c));
1234 let result = solver.solve();
1235 assert_eq!(result.solutions.len(), 1);
1236 let sol = &result.solutions[0];
1237 assert!(
1238 sol.get(a).expect("test: should succeed") < sol.get(b).expect("test: should succeed")
1239 );
1240 assert!(
1241 sol.get(b).expect("test: should succeed") < sol.get(c).expect("test: should succeed")
1242 );
1243 }
1244
1245 #[test]
1246 fn test_combined_all_different_and_less_than() {
1247 let mut solver = ConstraintSolver::new(SolverConfig {
1248 max_solutions: 10,
1249 ..SolverConfig::default()
1250 });
1251 let x = solver.add_variable("x".to_string(), vec![1, 2, 3, 4]);
1252 let y = solver.add_variable("y".to_string(), vec![1, 2, 3, 4]);
1253 let z = solver.add_variable("z".to_string(), vec![1, 2, 3, 4]);
1254 solver.add_constraint(Constraint::AllDifferent(vec![x, y, z]));
1255 solver.add_constraint(Constraint::LessThan(x, y));
1256 let result = solver.solve();
1257 assert!(!result.solutions.is_empty());
1258 for sol in &result.solutions {
1259 let vx = sol.get(x).expect("test: should succeed");
1260 let vy = sol.get(y).expect("test: should succeed");
1261 let vz = sol.get(z).expect("test: should succeed");
1262 assert_ne!(vx, vy);
1263 assert_ne!(vx, vz);
1264 assert_ne!(vy, vz);
1265 assert!(vx < vy);
1266 }
1267 }
1268
1269 #[test]
1270 fn test_single_variable_no_constraint() {
1271 let mut solver = default_solver();
1272 let x = solver.add_variable("x".to_string(), vec![7]);
1273 let result = solver.solve();
1274 assert_eq!(result.solutions.len(), 1);
1275 assert_eq!(result.solutions[0].get(x), Some(7));
1276 }
1277
1278 #[test]
1279 fn test_backtrack_count_increments() {
1280 let mut solver = ConstraintSolver::new(SolverConfig {
1281 max_solutions: 100,
1282 use_ac3: false,
1283 ..SolverConfig::default()
1284 });
1285 let x = solver.add_variable("x".to_string(), vec![1, 2]);
1286 let y = solver.add_variable("y".to_string(), vec![1, 2]);
1287 solver.add_constraint(Constraint::NotEqual(x, y));
1288 let result = solver.solve();
1289 assert!(result.backtracks < u64::MAX);
1292 let _ = result.constraint_checks;
1293 }
1294
1295 #[test]
1296 fn test_time_ms_is_set() {
1297 let mut solver = default_solver();
1298 solver.add_variable("x".to_string(), vec![1]);
1299 let result = solver.solve();
1300 let _ = result.time_ms;
1302 }
1303
1304 #[test]
1305 fn test_constraint_involves() {
1306 let c = Constraint::NotEqual(CspVarId(0), CspVarId(1));
1307 assert!(c.involves(CspVarId(0)));
1308 assert!(c.involves(CspVarId(1)));
1309 assert!(!c.involves(CspVarId(2)));
1310 }
1311
1312 #[test]
1313 fn test_constraint_variables_all_different() {
1314 let c = Constraint::AllDifferent(vec![CspVarId(0), CspVarId(2), CspVarId(4)]);
1315 let vars = c.variables();
1316 assert!(vars.contains(&CspVarId(0)));
1317 assert!(vars.contains(&CspVarId(2)));
1318 assert!(vars.contains(&CspVarId(4)));
1319 assert_eq!(vars.len(), 3);
1320 }
1321
1322 #[test]
1323 fn test_solver_config_default() {
1324 let cfg = SolverConfig::default();
1325 assert_eq!(cfg.max_solutions, 1);
1326 assert!(cfg.use_ac3);
1327 assert!(cfg.use_mrv);
1328 assert!(!cfg.use_lcv);
1329 assert_eq!(cfg.max_backtracks, 100_000);
1330 }
1331
1332 #[test]
1333 fn test_less_equal_equal_values_satisfiable() {
1334 let mut solver = default_solver();
1335 let a = solver.add_variable("a".to_string(), vec![3, 4]);
1336 let b = solver.add_variable("b".to_string(), vec![3, 4]);
1337 solver.add_constraint(Constraint::LessEqual(a, b));
1338 let result = solver.solve();
1339 assert!(!result.solutions.is_empty());
1340 let sol = &result.solutions[0];
1341 assert!(
1342 sol.get(a).expect("test: should succeed") <= sol.get(b).expect("test: should succeed")
1343 );
1344 }
1345
1346 #[test]
1347 fn test_multiple_in_domain_constraints() {
1348 let mut solver = default_solver();
1349 let x = solver.add_variable("x".to_string(), vec![1, 2, 3, 4, 5]);
1350 solver.add_constraint(Constraint::InDomain {
1351 var: x,
1352 allowed: vec![2, 3, 4],
1353 });
1354 solver.add_constraint(Constraint::InDomain {
1355 var: x,
1356 allowed: vec![3, 4, 5],
1357 });
1358 let result = solver.solve();
1359 assert!(!result.solutions.is_empty());
1360 let val = result.solutions[0].get(x).unwrap_or(-1);
1361 assert!(val == 3 || val == 4);
1363 }
1364
1365 #[test]
1366 fn test_solver_result_new() {
1367 let r = SolverResult::new();
1368 assert!(r.solutions.is_empty());
1369 assert_eq!(r.backtracks, 0);
1370 assert_eq!(r.constraint_checks, 0);
1371 assert_eq!(r.time_ms, 0);
1372 }
1373
1374 #[test]
1375 fn test_assignment_iter() {
1376 let mut a = Assignment::new();
1377 a.set(CspVarId(0), 10);
1378 a.set(CspVarId(1), 20);
1379 let mut collected: Vec<(CspVarId, i64)> = a.iter().collect();
1380 collected.sort_by_key(|(id, _)| id.0);
1381 assert_eq!(collected, vec![(CspVarId(0), 10), (CspVarId(1), 20)]);
1382 }
1383}