1use std::collections::HashMap;
24use std::sync::atomic::{AtomicU64, Ordering};
25
26static NEXT_VAR_ID: AtomicU64 = AtomicU64::new(1);
29static NEXT_SYM_ID: AtomicU64 = AtomicU64::new(1);
30static NEXT_CONSTRAINT_ID: AtomicU64 = AtomicU64::new(1);
31
32const NEAR_ZERO: f64 = 1.0e-8;
34
35fn near_zero(v: f64) -> bool {
36 v.abs() < NEAR_ZERO
37}
38
39#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
42enum SymKind {
43 Invalid,
44 External,
45 Slack,
46 Error,
47 Dummy,
48}
49
50#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
51struct Sym(u64, SymKind);
52
53impl Sym {
54 fn invalid() -> Self {
55 Sym(0, SymKind::Invalid)
56 }
57
58 fn is_invalid(self) -> bool {
59 self.1 == SymKind::Invalid
60 }
61
62 fn is_external(self) -> bool {
63 self.1 == SymKind::External
64 }
65
66 fn is_error(self) -> bool {
67 self.1 == SymKind::Error
68 }
69
70 fn is_dummy(self) -> bool {
71 self.1 == SymKind::Dummy
72 }
73
74 fn is_pivotable(self) -> bool {
75 matches!(self.1, SymKind::Slack | SymKind::Error)
76 }
77}
78
79fn new_sym(kind: SymKind) -> Sym {
80 Sym(NEXT_SYM_ID.fetch_add(1, Ordering::Relaxed), kind)
81}
82
83#[derive(Clone, Debug, Default)]
87struct Row {
88 cells: HashMap<Sym, f64>,
89 constant: f64,
90}
91
92impl Row {
93 fn new(constant: f64) -> Self {
94 Row {
95 cells: HashMap::new(),
96 constant,
97 }
98 }
99
100 fn add_constant(&mut self, v: f64) -> f64 {
102 self.constant += v;
103 self.constant
104 }
105
106 fn insert_symbol(&mut self, s: Sym, coeff: f64) {
107 let entry = self.cells.entry(s).or_insert(0.0);
108 *entry += coeff;
109 if near_zero(*entry) {
110 self.cells.remove(&s);
111 }
112 }
113
114 fn insert_row(&mut self, other: &Row, coeff: f64) {
115 self.constant += other.constant * coeff;
116 for (&s, &c) in &other.cells {
117 let entry = self.cells.entry(s).or_insert(0.0);
118 *entry += c * coeff;
119 if near_zero(*entry) {
120 self.cells.remove(&s);
121 }
122 }
123 }
124
125 fn remove(&mut self, s: Sym) {
126 self.cells.remove(&s);
127 }
128
129 fn reverse_sign(&mut self) {
130 self.constant = -self.constant;
131 for v in self.cells.values_mut() {
132 *v = -*v;
133 }
134 }
135
136 fn solve_for_symbol(&mut self, s: Sym) {
138 let c = self.cells.remove(&s).unwrap_or(1.0);
139 let factor = -1.0 / c;
140 self.constant *= factor;
141 for v in self.cells.values_mut() {
142 *v *= factor;
143 }
144 }
145
146 fn solve_for_symbols(&mut self, lhs: Sym, rhs: Sym) {
155 self.insert_symbol(lhs, -1.0);
156 self.solve_for_symbol(rhs);
157 }
158
159 fn coefficient_for(&self, s: Sym) -> f64 {
160 *self.cells.get(&s).unwrap_or(&0.0)
161 }
162
163 fn substitute(&mut self, s: Sym, row: &Row) {
164 if let Some(coeff) = self.cells.remove(&s) {
165 self.insert_row(row, coeff);
166 }
167 }
168}
169
170#[derive(Clone, Debug)]
174pub struct Variable {
175 id: u64,
176}
177
178impl Variable {
179 pub fn new() -> Self {
181 Variable {
182 id: NEXT_VAR_ID.fetch_add(1, Ordering::Relaxed),
183 }
184 }
185}
186
187impl Default for Variable {
188 fn default() -> Self {
189 Self::new()
190 }
191}
192
193impl PartialEq for Variable {
194 fn eq(&self, other: &Self) -> bool {
195 self.id == other.id
196 }
197}
198impl Eq for Variable {}
199impl std::hash::Hash for Variable {
200 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
201 self.id.hash(state);
202 }
203}
204
205#[derive(Clone, Debug)]
207pub struct Term {
208 pub variable: Variable,
210 pub coefficient: f64,
212}
213
214#[derive(Clone, Debug, Default)]
216pub struct Expression {
217 pub terms: Vec<Term>,
219 pub constant: f64,
221}
222
223impl Expression {
224 pub fn new(terms: Vec<Term>, constant: f64) -> Self {
226 Expression { terms, constant }
227 }
228
229 pub fn from_constant(c: f64) -> Self {
231 Expression {
232 terms: Vec::new(),
233 constant: c,
234 }
235 }
236
237 pub fn from_variable(v: Variable) -> Self {
239 Expression {
240 terms: vec![Term {
241 variable: v,
242 coefficient: 1.0,
243 }],
244 constant: 0.0,
245 }
246 }
247}
248
249#[derive(Clone, Copy, Debug, PartialEq)]
251pub enum RelOp {
252 LessThanOrEq,
254 GreaterThanOrEq,
256 Equal,
258}
259
260pub struct Strength;
262
263impl Strength {
264 pub const REQUIRED: f64 = 1_001_001_000.0;
266 pub const STRONG: f64 = 1_000_000.0;
268 pub const MEDIUM: f64 = 1_000.0;
270 pub const WEAK: f64 = 1.0;
272
273 pub fn clip(s: f64) -> f64 {
275 s.min(Self::REQUIRED)
276 }
277}
278
279#[derive(Clone, Debug)]
281pub struct Constraint {
282 expression: Expression,
283 op: RelOp,
284 strength: f64,
285 id: u64,
286}
287
288impl Constraint {
289 pub fn new(expression: Expression, op: RelOp, strength: f64) -> Self {
291 let id = NEXT_CONSTRAINT_ID.fetch_add(1, Ordering::Relaxed);
292 Constraint {
293 expression,
294 op,
295 strength: Strength::clip(strength),
296 id,
297 }
298 }
299}
300
301impl PartialEq for Constraint {
302 fn eq(&self, o: &Self) -> bool {
303 self.id == o.id
304 }
305}
306impl Eq for Constraint {}
307impl std::hash::Hash for Constraint {
308 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
309 self.id.hash(state);
310 }
311}
312
313#[derive(Clone, Debug, PartialEq)]
315pub enum SolverError {
316 DuplicateConstraint,
318 UnsatisfiableConstraint,
320 UnknownConstraint,
322 UnknownEditVariable,
324 InternalError(String),
326}
327
328impl std::fmt::Display for SolverError {
329 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
330 match self {
331 SolverError::DuplicateConstraint => write!(f, "duplicate constraint"),
332 SolverError::UnsatisfiableConstraint => {
333 write!(f, "unsatisfiable constraint (over-constrained)")
334 }
335 SolverError::UnknownConstraint => write!(f, "unknown constraint"),
336 SolverError::UnknownEditVariable => write!(f, "unknown edit variable"),
337 SolverError::InternalError(s) => write!(f, "internal solver error: {s}"),
338 }
339 }
340}
341
342impl std::error::Error for SolverError {}
343
344struct Tag {
347 marker: Sym,
348 other: Sym,
349}
350
351struct EditInfo {
352 tag: Tag,
353 constant: f64,
354}
355
356pub struct Solver {
364 rows: HashMap<Sym, Row>,
365 vars: HashMap<u64, Sym>,
367 constraints: HashMap<u64, Tag>,
369 edits: HashMap<u64, EditInfo>,
371 infeasible_rows: Vec<Sym>,
373 objective: Row,
375 values: HashMap<u64, f64>,
377}
378
379impl Solver {
380 pub fn new() -> Self {
382 Solver {
383 rows: HashMap::new(),
384 vars: HashMap::new(),
385 constraints: HashMap::new(),
386 edits: HashMap::new(),
387 infeasible_rows: Vec::new(),
388 objective: Row::new(0.0),
389 values: HashMap::new(),
390 }
391 }
392
393 pub fn add_constraint(&mut self, constraint: Constraint) -> Result<(), SolverError> {
397 if self.constraints.contains_key(&constraint.id) {
398 return Err(SolverError::DuplicateConstraint);
399 }
400
401 let (mut row, tag) = self.create_row(&constraint);
402 let subject = Self::choose_subject(&row, &tag);
403
404 if subject.is_invalid() {
405 if Self::all_dummies(&row) && !near_zero(row.constant) {
407 return Err(SolverError::UnsatisfiableConstraint);
408 }
409 if !self.add_with_artificial(&row)? {
411 return Err(SolverError::UnsatisfiableConstraint);
412 }
413 } else {
414 row.solve_for_symbol(subject);
415 self.substitute_all(subject, &row);
416 self.rows.insert(subject, row);
417 }
418
419 self.constraints.insert(constraint.id, tag);
420 self.optimize()?;
421 Ok(())
422 }
423
424 pub fn remove_constraint(&mut self, constraint: &Constraint) -> Result<(), SolverError> {
426 let tag = self
427 .constraints
428 .remove(&constraint.id)
429 .ok_or(SolverError::UnknownConstraint)?;
430
431 self.remove_constraint_effects(&tag, constraint.strength);
432
433 if self.rows.remove(&tag.marker).is_none() {
435 let (leaving, mut row) = self
437 .get_marker_leaving_row(tag.marker)
438 .ok_or_else(|| SolverError::InternalError("no leaving row for marker".into()))?;
439 row.solve_for_symbols(leaving, tag.marker);
440 self.substitute_all(tag.marker, &row);
441 self.rows.remove(&tag.marker);
443 }
444
445 self.optimize()?;
446 Ok(())
447 }
448
449 pub fn add_edit_variable(
454 &mut self,
455 variable: &Variable,
456 strength: f64,
457 ) -> Result<(), SolverError> {
458 if self.edits.contains_key(&variable.id) {
459 return Err(SolverError::DuplicateConstraint);
460 }
461 let strength = Strength::clip(strength);
462 if (strength - Strength::REQUIRED).abs() < NEAR_ZERO {
463 return Err(SolverError::InternalError(
464 "edit variables cannot be REQUIRED".into(),
465 ));
466 }
467
468 let expr = Expression::from_variable(variable.clone());
469 let c = Constraint::new(expr, RelOp::Equal, strength);
470 self.add_constraint(c.clone())?;
471
472 let tag = self
473 .constraints
474 .get(&c.id)
475 .ok_or_else(|| SolverError::InternalError("tag not found after add".into()))?;
476 let tag = Tag {
477 marker: tag.marker,
478 other: tag.other,
479 };
480 self.edits
481 .insert(variable.id, EditInfo { tag, constant: 0.0 });
482 Ok(())
483 }
484
485 pub fn suggest_value(&mut self, variable: &Variable, value: f64) -> Result<(), SolverError> {
487 let (delta, marker, other) = {
488 let info = self
489 .edits
490 .get_mut(&variable.id)
491 .ok_or(SolverError::UnknownEditVariable)?;
492 let delta = value - info.constant;
493 info.constant = value;
494 (delta, info.tag.marker, info.tag.other)
495 };
496
497 if let Some(row) = self.rows.get_mut(&marker) {
500 if row.add_constant(-delta) < 0.0 {
501 self.infeasible_rows.push(marker);
502 }
503 return self.dual_optimize();
504 }
505 if !other.is_invalid() {
507 if let Some(row) = self.rows.get_mut(&other) {
508 if row.add_constant(delta) < 0.0 {
509 self.infeasible_rows.push(other);
510 }
511 return self.dual_optimize();
512 }
513 }
514 let keys: Vec<Sym> = self.rows.keys().cloned().collect();
516 for sym in keys {
517 let row = self.rows.get_mut(&sym).expect("row present");
518 let coeff = row.coefficient_for(marker);
519 let diff = delta * coeff;
520 if !near_zero(diff) {
521 row.add_constant(diff);
522 if !sym.is_external() && row.constant < 0.0 {
523 self.infeasible_rows.push(sym);
524 }
525 }
526 }
527 self.dual_optimize()
528 }
529
530 pub fn update_variables(&mut self) {
535 let pairs: Vec<(u64, Sym)> = self.vars.iter().map(|(&id, &s)| (id, s)).collect();
536 for (var_id, sym) in pairs {
537 let v = self.rows.get(&sym).map(|r| r.constant).unwrap_or(0.0);
538 self.values.insert(var_id, v);
539 }
540 }
541
542 pub fn value_of(&self, variable: &Variable) -> f64 {
544 *self.values.get(&variable.id).unwrap_or(&0.0)
545 }
546
547 pub fn reset(&mut self) {
549 self.rows.clear();
550 self.vars.clear();
551 self.constraints.clear();
552 self.edits.clear();
553 self.infeasible_rows.clear();
554 self.objective = Row::new(0.0);
555 self.values.clear();
556 }
557
558 fn get_var_sym(&mut self, variable: &Variable) -> Sym {
561 *self
562 .vars
563 .entry(variable.id)
564 .or_insert_with(|| new_sym(SymKind::External))
565 }
566
567 fn create_row(&mut self, constraint: &Constraint) -> (Row, Tag) {
568 let expr = &constraint.expression;
569 let mut row = Row::new(expr.constant);
570 for term in &expr.terms {
571 if !near_zero(term.coefficient) {
572 let sym = self.get_var_sym(&term.variable);
573 if let Some(basic) = self.rows.get(&sym).cloned() {
574 row.insert_row(&basic, term.coefficient);
575 } else {
576 row.insert_symbol(sym, term.coefficient);
577 }
578 }
579 }
580
581 let is_req = (constraint.strength - Strength::REQUIRED).abs() < NEAR_ZERO;
582
583 let tag = match constraint.op {
584 RelOp::GreaterThanOrEq | RelOp::LessThanOrEq => {
585 let coeff = if constraint.op == RelOp::LessThanOrEq {
586 1.0
587 } else {
588 -1.0
589 };
590 let slack = new_sym(SymKind::Slack);
591 row.insert_symbol(slack, coeff);
592 if !is_req {
593 let error = new_sym(SymKind::Error);
594 row.insert_symbol(error, -coeff);
595 self.objective.insert_symbol(error, constraint.strength);
596 Tag {
597 marker: slack,
598 other: error,
599 }
600 } else {
601 Tag {
602 marker: slack,
603 other: Sym::invalid(),
604 }
605 }
606 }
607 RelOp::Equal => {
608 if is_req {
609 let dummy = new_sym(SymKind::Dummy);
610 row.insert_symbol(dummy, 1.0);
611 Tag {
612 marker: dummy,
613 other: Sym::invalid(),
614 }
615 } else {
616 let ep = new_sym(SymKind::Error);
617 let em = new_sym(SymKind::Error);
618 row.insert_symbol(ep, -1.0);
619 row.insert_symbol(em, 1.0);
620 self.objective.insert_symbol(ep, constraint.strength);
621 self.objective.insert_symbol(em, constraint.strength);
622 Tag {
623 marker: ep,
624 other: em,
625 }
626 }
627 }
628 };
629
630 if row.constant < 0.0 {
631 row.reverse_sign();
632 }
633 (row, tag)
634 }
635
636 fn choose_subject(row: &Row, tag: &Tag) -> Sym {
637 for &sym in row.cells.keys() {
638 if sym.is_external() {
639 return sym;
640 }
641 }
642 if tag.marker.is_pivotable() && row.coefficient_for(tag.marker) < 0.0 {
643 return tag.marker;
644 }
645 if !tag.other.is_invalid()
646 && tag.other.is_pivotable()
647 && row.coefficient_for(tag.other) < 0.0
648 {
649 return tag.other;
650 }
651 Sym::invalid()
652 }
653
654 fn all_dummies(row: &Row) -> bool {
655 row.cells.keys().all(|s| s.is_dummy())
656 }
657
658 fn add_with_artificial(&mut self, row: &Row) -> Result<bool, SolverError> {
659 let art = new_sym(SymKind::Slack);
660 let mut art_obj = row.clone();
662 self.rows.insert(art, row.clone());
663
664 self.optimize_row(&mut art_obj)?;
666
667 let success = near_zero(art_obj.constant);
668
669 if let Some(mut art_row) = self.rows.remove(&art) {
671 if !art_row.cells.is_empty() {
672 let entering = Self::any_pivotable_sym(&art_row);
673 if !entering.is_invalid() {
674 art_row.solve_for_symbols(art, entering);
675 self.substitute_all(entering, &art_row);
676 self.rows.insert(entering, art_row);
677 }
678 }
679 }
680
681 for row in self.rows.values_mut() {
683 row.remove(art);
684 }
685 self.objective.remove(art);
686
687 Ok(success)
688 }
689
690 fn any_pivotable_sym(row: &Row) -> Sym {
691 row.cells
692 .keys()
693 .find(|&&s| s.is_pivotable())
694 .cloned()
695 .unwrap_or_else(Sym::invalid)
696 }
697
698 fn substitute_all(&mut self, sym: Sym, row: &Row) {
699 let keys: Vec<Sym> = self.rows.keys().cloned().collect();
700 for key in keys {
701 let r = self.rows.get_mut(&key).expect("row present");
702 r.substitute(sym, row);
703 if !key.is_external() && r.constant < 0.0 {
704 self.infeasible_rows.push(key);
705 }
706 }
707 self.objective.substitute(sym, row);
708 }
709
710 fn optimize(&mut self) -> Result<(), SolverError> {
711 loop {
712 let entering = Self::get_entering_sym(&self.objective);
713 if entering.is_invalid() {
714 return Ok(());
715 }
716 let (leaving, mut row) = self
717 .get_leaving_row(entering)
718 .ok_or_else(|| SolverError::InternalError("objective unbounded".into()))?;
719 row.solve_for_symbols(leaving, entering);
720 self.substitute_all(entering, &row);
721 self.rows.insert(entering, row);
722 }
723 }
724
725 fn optimize_row(&mut self, obj: &mut Row) -> Result<(), SolverError> {
726 loop {
727 let entering = Self::get_entering_sym(obj);
728 if entering.is_invalid() {
729 return Ok(());
730 }
731 let (leaving, mut row) = self
732 .get_leaving_row(entering)
733 .ok_or_else(|| SolverError::InternalError("art objective unbounded".into()))?;
734 row.solve_for_symbols(leaving, entering);
735 self.substitute_all(entering, &row);
736 obj.substitute(entering, &row);
737 self.rows.insert(entering, row);
738 }
739 }
740
741 fn dual_optimize(&mut self) -> Result<(), SolverError> {
742 while let Some(leaving) = self.infeasible_rows.pop() {
743 let constant = match self.rows.get(&leaving) {
745 Some(r) => r.constant,
746 None => continue,
747 };
748 if constant >= 0.0 {
749 continue;
750 }
751
752 let entering = {
755 let row = self.rows.get(&leaving).expect("infeasible row");
756 let mut best = Sym::invalid();
757 let mut ratio = f64::INFINITY;
758 for (&sym, &coeff) in &row.cells {
759 if coeff > 0.0 && !sym.is_dummy() {
760 let obj_c = self.objective.coefficient_for(sym);
761 let r = obj_c / coeff;
762 if r < ratio {
763 ratio = r;
764 best = sym;
765 }
766 }
767 }
768 best
769 };
770
771 if entering.is_invalid() {
772 return Err(SolverError::InternalError("dual optimize failed".into()));
773 }
774
775 let mut row = self
776 .rows
777 .remove(&leaving)
778 .ok_or_else(|| SolverError::InternalError("leaving row missing".into()))?;
779 row.solve_for_symbols(leaving, entering);
780 self.substitute_all(entering, &row);
781 self.rows.insert(entering, row);
782 }
783 Ok(())
784 }
785
786 fn get_entering_sym(obj: &Row) -> Sym {
787 for (&sym, &coeff) in &obj.cells {
788 if !sym.is_dummy() && coeff < 0.0 {
789 return sym;
790 }
791 }
792 Sym::invalid()
793 }
794
795 fn get_leaving_row(&mut self, entering: Sym) -> Option<(Sym, Row)> {
796 let mut ratio = f64::INFINITY;
797 let mut found = None;
798 for (&sym, row) in &self.rows {
799 if sym.is_external() {
800 continue;
801 }
802 let c = row.coefficient_for(entering);
803 if c < 0.0 {
804 let r = -row.constant / c;
805 if r < ratio {
806 ratio = r;
807 found = Some(sym);
808 }
809 }
810 }
811 found.map(|s| (s, self.rows.remove(&s).expect("row present")))
812 }
813
814 fn get_marker_leaving_row(&mut self, marker: Sym) -> Option<(Sym, Row)> {
815 let mut r1 = f64::INFINITY;
816 let mut r2 = f64::INFINITY;
817 let mut first: Option<Sym> = None;
818 let mut second: Option<Sym> = None;
819 let mut third: Option<Sym> = None;
820
821 for (&sym, row) in &self.rows {
822 let c = row.coefficient_for(marker);
823 if near_zero(c) {
824 continue;
825 }
826 if sym.is_external() {
827 third = Some(sym);
828 } else if c < 0.0 {
829 let r = -row.constant / c;
830 if r < r1 {
831 r1 = r;
832 first = Some(sym);
833 }
834 } else {
835 let r = row.constant / c;
836 if r < r2 {
837 r2 = r;
838 second = Some(sym);
839 }
840 }
841 }
842
843 first
844 .or(second)
845 .or(third)
846 .map(|s| (s, self.rows.remove(&s).expect("row present")))
847 }
848
849 fn remove_constraint_effects(&mut self, tag: &Tag, strength: f64) {
850 if tag.marker.is_error() {
851 self.remove_marker_effects(tag.marker, strength);
852 } else if tag.other.is_error() {
853 self.remove_marker_effects(tag.other, strength);
854 }
855 }
856
857 fn remove_marker_effects(&mut self, marker: Sym, strength: f64) {
858 if let Some(row) = self.rows.get(&marker).cloned() {
859 self.objective.insert_row(&row, -strength);
860 } else {
861 self.objective.insert_symbol(marker, -strength);
862 }
863 }
864}
865
866impl Default for Solver {
867 fn default() -> Self {
868 Self::new()
869 }
870}
871
872#[cfg(test)]
875mod tests {
876 use super::*;
877
878 fn approx(a: f64, b: f64) -> bool {
879 (a - b).abs() < 1e-4
880 }
881
882 #[test]
884 fn two_equality_constraints() {
885 let mut s = Solver::new();
886 let x = Variable::new();
887 let y = Variable::new();
888
889 s.add_constraint(Constraint::new(
891 Expression::new(
892 vec![Term {
893 variable: x.clone(),
894 coefficient: 1.0,
895 }],
896 -10.0,
897 ),
898 RelOp::Equal,
899 Strength::REQUIRED,
900 ))
901 .unwrap();
902 s.add_constraint(Constraint::new(
904 Expression::new(
905 vec![
906 Term {
907 variable: y.clone(),
908 coefficient: 1.0,
909 },
910 Term {
911 variable: x.clone(),
912 coefficient: -1.0,
913 },
914 ],
915 -5.0,
916 ),
917 RelOp::Equal,
918 Strength::REQUIRED,
919 ))
920 .unwrap();
921
922 s.update_variables();
923 assert!(approx(s.value_of(&x), 10.0), "x={}", s.value_of(&x));
924 assert!(approx(s.value_of(&y), 15.0), "y={}", s.value_of(&y));
925 }
926
927 #[test]
929 fn suggest_value_and_update() {
930 let mut s = Solver::new();
931 let x = Variable::new();
932
933 s.add_constraint(Constraint::new(
935 Expression::from_variable(x.clone()),
936 RelOp::Equal,
937 Strength::WEAK,
938 ))
939 .unwrap();
940
941 s.add_edit_variable(&x, Strength::STRONG).unwrap();
942 s.suggest_value(&x, 30.0).unwrap();
943 s.update_variables();
944
945 assert!(approx(s.value_of(&x), 30.0), "x={}", s.value_of(&x));
946 }
947
948 #[test]
950 fn over_constrained_required() {
951 let mut s = Solver::new();
952 let x = Variable::new();
953
954 s.add_constraint(Constraint::new(
955 Expression::new(
956 vec![Term {
957 variable: x.clone(),
958 coefficient: 1.0,
959 }],
960 -10.0,
961 ),
962 RelOp::Equal,
963 Strength::REQUIRED,
964 ))
965 .unwrap();
966
967 let result = s.add_constraint(Constraint::new(
968 Expression::new(
969 vec![Term {
970 variable: x.clone(),
971 coefficient: 1.0,
972 }],
973 -20.0,
974 ),
975 RelOp::Equal,
976 Strength::REQUIRED,
977 ));
978 assert_eq!(result, Err(SolverError::UnsatisfiableConstraint));
979 }
980
981 #[test]
983 fn proportional_constraints() {
984 let mut s = Solver::new();
985 let a = Variable::new();
986 let b = Variable::new();
987
988 s.add_constraint(Constraint::new(
990 Expression::new(
991 vec![
992 Term {
993 variable: a.clone(),
994 coefficient: 1.0,
995 },
996 Term {
997 variable: b.clone(),
998 coefficient: -2.0,
999 },
1000 ],
1001 0.0,
1002 ),
1003 RelOp::Equal,
1004 Strength::REQUIRED,
1005 ))
1006 .unwrap();
1007 s.add_constraint(Constraint::new(
1009 Expression::new(
1010 vec![
1011 Term {
1012 variable: a.clone(),
1013 coefficient: 1.0,
1014 },
1015 Term {
1016 variable: b.clone(),
1017 coefficient: 1.0,
1018 },
1019 ],
1020 -300.0,
1021 ),
1022 RelOp::Equal,
1023 Strength::REQUIRED,
1024 ))
1025 .unwrap();
1026
1027 s.update_variables();
1028 assert!(approx(s.value_of(&a), 200.0), "a={}", s.value_of(&a));
1029 assert!(approx(s.value_of(&b), 100.0), "b={}", s.value_of(&b));
1030 }
1031
1032 #[test]
1034 fn anchoring_with_bounds() {
1035 let mut s = Solver::new();
1036 let x = Variable::new();
1037
1038 s.add_constraint(Constraint::new(
1040 Expression::new(
1041 vec![Term {
1042 variable: x.clone(),
1043 coefficient: 1.0,
1044 }],
1045 -50.0,
1046 ),
1047 RelOp::GreaterThanOrEq,
1048 Strength::REQUIRED,
1049 ))
1050 .unwrap();
1051 s.add_constraint(Constraint::new(
1053 Expression::new(
1054 vec![Term {
1055 variable: x.clone(),
1056 coefficient: 1.0,
1057 }],
1058 -200.0,
1059 ),
1060 RelOp::LessThanOrEq,
1061 Strength::REQUIRED,
1062 ))
1063 .unwrap();
1064
1065 s.add_edit_variable(&x, Strength::STRONG).unwrap();
1066 s.suggest_value(&x, 75.0).unwrap();
1067 s.update_variables();
1068
1069 let v = s.value_of(&x);
1070 assert!(v >= 50.0 - 1e-4, "x={v} < 50");
1071 assert!(v <= 200.0 + 1e-4, "x={v} > 200");
1072 assert!(approx(v, 75.0), "x={v} ≠ 75");
1073 }
1074
1075 #[test]
1077 fn remove_constraint_and_resolv() {
1078 let mut s = Solver::new();
1079 let x = Variable::new();
1080
1081 let c1 = Constraint::new(
1082 Expression::new(
1083 vec![Term {
1084 variable: x.clone(),
1085 coefficient: 1.0,
1086 }],
1087 -10.0,
1088 ),
1089 RelOp::Equal,
1090 Strength::REQUIRED,
1091 );
1092 let c2 = Constraint::new(
1093 Expression::new(
1094 vec![Term {
1095 variable: x.clone(),
1096 coefficient: 1.0,
1097 }],
1098 -20.0,
1099 ),
1100 RelOp::Equal,
1101 Strength::REQUIRED,
1102 );
1103
1104 s.add_constraint(c1.clone()).unwrap();
1105 s.update_variables();
1106 assert!(
1107 approx(s.value_of(&x), 10.0),
1108 "before remove: x={}",
1109 s.value_of(&x)
1110 );
1111
1112 s.remove_constraint(&c1).unwrap();
1113 s.add_constraint(c2).unwrap();
1114 s.update_variables();
1115 assert!(
1116 approx(s.value_of(&x), 20.0),
1117 "after remove: x={}",
1118 s.value_of(&x)
1119 );
1120 }
1121
1122 #[test]
1124 fn strength_ordering() {
1125 let mut s = Solver::new();
1126 let x = Variable::new();
1127
1128 s.add_constraint(Constraint::new(
1130 Expression::new(
1131 vec![Term {
1132 variable: x.clone(),
1133 coefficient: 1.0,
1134 }],
1135 -100.0,
1136 ),
1137 RelOp::Equal,
1138 Strength::REQUIRED,
1139 ))
1140 .unwrap();
1141 s.add_constraint(Constraint::new(
1143 Expression::new(
1144 vec![Term {
1145 variable: x.clone(),
1146 coefficient: 1.0,
1147 }],
1148 -200.0,
1149 ),
1150 RelOp::Equal,
1151 Strength::STRONG,
1152 ))
1153 .unwrap();
1154
1155 s.update_variables();
1156 assert!(approx(s.value_of(&x), 100.0), "x={}", s.value_of(&x));
1157 }
1158
1159 #[test]
1161 fn reset_and_readd() {
1162 let mut s = Solver::new();
1163 let x = Variable::new();
1164
1165 s.add_constraint(Constraint::new(
1166 Expression::new(
1167 vec![Term {
1168 variable: x.clone(),
1169 coefficient: 1.0,
1170 }],
1171 -99.0,
1172 ),
1173 RelOp::Equal,
1174 Strength::REQUIRED,
1175 ))
1176 .unwrap();
1177 s.update_variables();
1178 assert!(approx(s.value_of(&x), 99.0));
1179
1180 s.reset();
1181
1182 s.add_constraint(Constraint::new(
1183 Expression::new(
1184 vec![Term {
1185 variable: x.clone(),
1186 coefficient: 1.0,
1187 }],
1188 -5.0,
1189 ),
1190 RelOp::Equal,
1191 Strength::REQUIRED,
1192 ))
1193 .unwrap();
1194 s.update_variables();
1195 assert!(approx(s.value_of(&x), 5.0), "x={}", s.value_of(&x));
1196 }
1197
1198 use proptest::prelude::*;
1201
1202 proptest! {
1203 #[test]
1204 fn no_panic_random_constraints(
1205 vals in prop::collection::vec(
1206 (0.0f64..100.0, 0.0f64..100.0), 1..10
1207 )
1208 ) {
1209 let mut s = Solver::new();
1210 let x = Variable::new();
1211 for (lo, hi) in &vals {
1212 let lo = lo.min(*hi);
1213 let hi = hi.max(lo);
1214 let _ = s.add_constraint(Constraint::new(
1215 Expression::new(
1216 vec![Term { variable: x.clone(), coefficient: 1.0 }],
1217 -lo,
1218 ),
1219 RelOp::GreaterThanOrEq,
1220 Strength::WEAK,
1221 ));
1222 let _ = s.add_constraint(Constraint::new(
1223 Expression::new(
1224 vec![Term { variable: x.clone(), coefficient: 1.0 }],
1225 -hi,
1226 ),
1227 RelOp::LessThanOrEq,
1228 Strength::WEAK,
1229 ));
1230 }
1231 s.update_variables();
1232 }
1233 }
1234}