1use super::*;
2
3fn validate_one_hot_non_empty(constraint: &crate::OneHotConstraint) -> crate::Result<()> {
4 crate::ensure!(
5 !constraint.variables.is_empty(),
6 crate::OneHotConstraintError::EmptyVariables
7 );
8 Ok(())
9}
10
11fn validate_sos1_non_empty(constraint: &crate::Sos1Constraint) -> crate::Result<()> {
12 crate::ensure!(
13 !constraint.variables.is_empty(),
14 crate::Sos1ConstraintError::EmptyVariables
15 );
16 Ok(())
17}
18
19impl Instance {
20 fn validate_required_ids_with_sets(
28 required_ids: &VariableIDSet,
29 variable_ids: &VariableIDSet,
30 dependency_keys: &VariableIDSet,
31 fixed_ids: &VariableIDSet,
32 ) -> crate::Result<()> {
33 if !required_ids.is_subset(variable_ids) {
35 let id = *required_ids.difference(variable_ids).next().unwrap();
36 crate::bail!({ ?id }, "Undefined variable ID is used: {id:?}");
37 }
38
39 if let Some(&id) = required_ids.intersection(dependency_keys).next() {
41 crate::bail!(
42 { ?id },
43 "Dependent variable cannot be used in objectives or constraints: {id:?}",
44 );
45 }
46
47 if let Some(&id) = required_ids.intersection(fixed_ids).next() {
49 crate::bail!(
50 { ?id },
51 "Fixed variable {id:?} cannot be used in objectives or constraints",
52 );
53 }
54
55 Ok(())
56 }
57
58 fn validate_required_ids(&self, required_ids: VariableIDSet) -> crate::Result<()> {
63 let variable_ids: VariableIDSet = self.decision_variables.keys().cloned().collect();
64 let dependency_keys: VariableIDSet = self.decision_variable_dependency.keys().collect();
65 let fixed_ids: VariableIDSet = self
66 .fixed_decision_variable_values()
67 .keys()
68 .copied()
69 .collect();
70 Self::validate_required_ids_with_sets(
71 &required_ids,
72 &variable_ids,
73 &dependency_keys,
74 &fixed_ids,
75 )
76 }
77
78 pub fn set_objective(&mut self, objective: Function) -> crate::Result<()> {
112 self.validate_required_ids(objective.required_ids())?;
114 self.objective = objective;
115 self.output_objective = None;
116 Ok(())
117 }
118
119 pub fn add_constraint(
130 &mut self,
131 constraint: Constraint,
132 context: crate::ConstraintContext,
133 ) -> crate::Result<ConstraintID> {
134 self.validate_required_ids(constraint.required_ids())?;
135 let id = self.constraint_collection.unused_id();
136 self.constraint_collection
137 .insert_active_with_context(id, constraint, context)?;
138 Ok(id)
139 }
140
141 fn require_binary_variable(&self, id: VariableID) -> crate::Result<()> {
147 let dv = self
148 .decision_variables
149 .get(&id)
150 .ok_or_else(|| crate::error!("Variable {id:?} is not defined in decision_variables"))?;
151 if dv.kind() != crate::decision_variable::Kind::Binary {
152 crate::bail!({ ?id }, "Variable {id:?} must be binary");
153 }
154 Ok(())
155 }
156
157 pub fn add_indicator_constraint(
166 &mut self,
167 constraint: crate::IndicatorConstraint,
168 context: crate::ConstraintContext,
169 ) -> crate::Result<crate::IndicatorConstraintID> {
170 self.validate_required_ids(constraint.required_ids())?;
171 self.require_binary_variable(constraint.indicator_variable)?;
172 let id = self.indicator_constraint_collection.unused_id();
173 self.indicator_constraint_collection
174 .insert_active_with_context(id, constraint, context)?;
175 Ok(id)
176 }
177
178 pub fn add_one_hot_constraint(
185 &mut self,
186 constraint: crate::OneHotConstraint,
187 context: crate::ConstraintContext,
188 ) -> crate::Result<crate::OneHotConstraintID> {
189 validate_one_hot_non_empty(&constraint)?;
190 self.validate_required_ids(constraint.required_ids())?;
191 for var_id in &constraint.variables {
192 self.require_binary_variable(*var_id)?;
193 }
194 let id = self.one_hot_constraint_collection.unused_id();
195 self.one_hot_constraint_collection
196 .insert_active_with_context(id, constraint, context)?;
197 Ok(id)
198 }
199
200 pub fn add_sos1_constraint(
206 &mut self,
207 constraint: crate::Sos1Constraint,
208 context: crate::ConstraintContext,
209 ) -> crate::Result<crate::Sos1ConstraintID> {
210 validate_sos1_non_empty(&constraint)?;
211 self.validate_required_ids(constraint.required_ids())?;
212 let id = self.sos1_constraint_collection.unused_id();
213 self.sos1_constraint_collection
214 .insert_active_with_context(id, constraint, context)?;
215 Ok(id)
216 }
217
218 pub fn add_decision_variable(
230 &mut self,
231 id: crate::VariableID,
232 variable: crate::DecisionVariable,
233 label: crate::DecisionVariableLabel,
234 ) -> crate::Result<crate::VariableID> {
235 if !self.decision_variables.contains_key(&id)
236 && self.decision_variable_dependency.keys().any(|k| k == id)
237 {
238 crate::bail!(
239 { ?id },
240 "Variable id {id:?} is currently used as a substitution-dependency key",
241 );
242 }
243 self.decision_variables
244 .insert(id, variable, label, None, crate::ATol::default())?;
245 Ok(id)
246 }
247
248 fn apply_validated_constraint_insert(
249 &mut self,
250 id: ConstraintID,
251 constraint: Constraint,
252 ) -> crate::Result<Option<Constraint>> {
253 if self.constraint_collection.active().contains_key(&id)
254 || self.constraint_collection.removed().contains_key(&id)
255 {
256 let old = self
257 .constraint_collection
258 .replace_row_preserving_lifecycle(id, constraint)
259 .expect("read-only membership check found this constraint");
260 Ok(Some(old))
261 } else {
262 self.constraint_collection.insert_active_with_context(
263 id,
264 constraint,
265 crate::ConstraintContext::default(),
266 )?;
267 Ok(None)
268 }
269 }
270
271 pub fn insert_constraint(
278 &mut self,
279 id: ConstraintID,
280 constraint: Constraint,
281 ) -> crate::Result<Option<Constraint>> {
282 self.validate_required_ids(constraint.required_ids())?;
284 self.apply_validated_constraint_insert(id, constraint)
285 }
286
287 pub fn insert_constraints(
304 &mut self,
305 constraints: Vec<(ConstraintID, Constraint)>,
306 ) -> crate::Result<BTreeMap<ConstraintID, Constraint>> {
307 let variable_ids: VariableIDSet = self.decision_variables.keys().cloned().collect();
309 let dependency_keys: VariableIDSet = self.decision_variable_dependency.keys().collect();
310 let fixed_ids: VariableIDSet = self
311 .fixed_decision_variable_values()
312 .keys()
313 .copied()
314 .collect();
315
316 for (_, constraint) in &constraints {
318 let required_ids = constraint.required_ids();
319 Self::validate_required_ids_with_sets(
320 &required_ids,
321 &variable_ids,
322 &dependency_keys,
323 &fixed_ids,
324 )?;
325 }
326
327 let mut replaced = BTreeMap::new();
329 for (id, constraint) in constraints {
330 if let Some(old_constraint) = self.apply_validated_constraint_insert(id, constraint)? {
331 replaced.insert(id, old_constraint);
332 }
333 }
334
335 Ok(replaced)
336 }
337
338 pub fn next_constraint_id(&self) -> ConstraintID {
346 let max_in_constraints = self
347 .constraints()
348 .last_key_value()
349 .map(|(id, _)| id.into_inner());
350 let max_in_removed = self
351 .removed_constraints()
352 .last_key_value()
353 .map(|(id, _)| id.into_inner());
354
355 max_in_constraints
356 .max(max_in_removed)
357 .map(|max| ConstraintID::from(max + 1))
358 .unwrap_or(ConstraintID::from(0))
359 }
360}
361
362impl ParametricInstance {
363 fn validate_required_ids(&self, required_ids: VariableIDSet) -> crate::Result<()> {
373 let variable_ids: VariableIDSet = self.decision_variables().keys().cloned().collect();
374 let parameter_ids: VariableIDSet = self.parameters().keys().cloned().collect();
375 let known_ids: VariableIDSet = variable_ids.union(¶meter_ids).cloned().collect();
376 let dependency_keys: VariableIDSet = self.decision_variable_dependency().keys().collect();
377 let fixed_ids: VariableIDSet = self
378 .fixed_decision_variable_values()
379 .keys()
380 .copied()
381 .collect();
382
383 if !required_ids.is_subset(&known_ids) {
384 let id = *required_ids.difference(&known_ids).next().unwrap();
385 crate::bail!({ ?id }, "Undefined variable ID is used: {id:?}");
386 }
387 if let Some(&id) = required_ids.intersection(&dependency_keys).next() {
388 crate::bail!(
389 { ?id },
390 "Dependent variable cannot be used in objectives or constraints: {id:?}",
391 );
392 }
393 if let Some(&id) = required_ids.intersection(&fixed_ids).next() {
394 crate::bail!(
395 { ?id },
396 "Fixed variable {id:?} cannot be used in objectives or constraints",
397 );
398 }
399 Ok(())
400 }
401
402 pub fn add_constraint(
414 &mut self,
415 constraint: Constraint,
416 context: crate::ConstraintContext,
417 ) -> crate::Result<ConstraintID> {
418 self.validate_required_ids(constraint.required_ids())?;
419 let id = self.constraint_collection.unused_id();
420 self.constraint_collection
421 .insert_active_with_context(id, constraint, context)?;
422 Ok(id)
423 }
424
425 fn require_decision_variables(&self, ids: VariableIDSet) -> crate::Result<()> {
433 let variable_ids: VariableIDSet = self.decision_variables().keys().cloned().collect();
434 if !ids.is_subset(&variable_ids) {
435 let id = *ids.difference(&variable_ids).next().unwrap();
436 if self.parameters().contains_key(&id) {
437 crate::bail!(
438 { ?id },
439 "Parameter id {id:?} cannot occupy a structural variable position; \
440 structural variables in indicator / one-hot / SOS1 constraints \
441 must be decision variables",
442 );
443 }
444 crate::bail!({ ?id }, "Undefined variable ID is used: {id:?}");
445 }
446 Ok(())
447 }
448
449 fn require_binary_variable(&self, id: VariableID) -> crate::Result<()> {
456 let dv = self.decision_variables().get(&id).ok_or_else(|| {
457 if self.parameters().contains_key(&id) {
458 crate::error!(
459 "Parameter id {id:?} cannot occupy a structural variable position; \
460 it must be a binary decision variable",
461 )
462 } else {
463 crate::error!("Variable {id:?} is not defined in decision_variables")
464 }
465 })?;
466 if dv.kind() != crate::decision_variable::Kind::Binary {
467 crate::bail!({ ?id }, "Variable {id:?} must be binary");
468 }
469 Ok(())
470 }
471
472 pub fn add_indicator_constraint(
480 &mut self,
481 constraint: crate::IndicatorConstraint,
482 context: crate::ConstraintContext,
483 ) -> crate::Result<crate::IndicatorConstraintID> {
484 self.require_binary_variable(constraint.indicator_variable)?;
487 self.validate_required_ids(constraint.required_ids())?;
493 let id = self.indicator_constraint_collection.unused_id();
494 self.indicator_constraint_collection
495 .insert_active_with_context(id, constraint, context)?;
496 Ok(id)
497 }
498
499 pub fn add_one_hot_constraint(
505 &mut self,
506 constraint: crate::OneHotConstraint,
507 context: crate::ConstraintContext,
508 ) -> crate::Result<crate::OneHotConstraintID> {
509 validate_one_hot_non_empty(&constraint)?;
510 for var_id in &constraint.variables {
511 self.require_binary_variable(*var_id)?;
512 }
513 self.validate_required_ids(constraint.required_ids())?;
514 let id = self.one_hot_constraint_collection.unused_id();
515 self.one_hot_constraint_collection
516 .insert_active_with_context(id, constraint, context)?;
517 Ok(id)
518 }
519
520 pub fn add_sos1_constraint(
527 &mut self,
528 constraint: crate::Sos1Constraint,
529 context: crate::ConstraintContext,
530 ) -> crate::Result<crate::Sos1ConstraintID> {
531 validate_sos1_non_empty(&constraint)?;
532 let required_ids = constraint.required_ids();
533 self.require_decision_variables(required_ids.clone())?;
534 self.validate_required_ids(required_ids)?;
535 let id = self.sos1_constraint_collection.unused_id();
536 self.sos1_constraint_collection
537 .insert_active_with_context(id, constraint, context)?;
538 Ok(id)
539 }
540
541 pub fn add_decision_variable(
554 &mut self,
555 id: crate::VariableID,
556 variable: crate::DecisionVariable,
557 label: crate::DecisionVariableLabel,
558 ) -> crate::Result<crate::VariableID> {
559 let has_decision_variable = self.decision_variables().contains_key(&id);
560 if !has_decision_variable && self.parameters().contains_key(&id) {
561 return Err(crate::ParameterIDCollision { id }.into());
562 }
563 if !has_decision_variable && self.decision_variable_dependency().keys().any(|k| k == id) {
564 crate::bail!(
565 { ?id },
566 "Variable id {id:?} is currently used as a substitution-dependency key",
567 );
568 }
569 self.decision_variables
570 .insert(id, variable, label, None, crate::ATol::default())?;
571 Ok(id)
572 }
573}
574
575#[cfg(test)]
576mod tests {
577 use super::*;
578 use crate::{
579 assign, coeff,
580 constraint::{Constraint, ConstraintID},
581 linear,
582 polynomial_base::{Linear, LinearMonomial},
583 DecisionVariable, Function, ParameterIDCollision, ParameterTable, Substitute, VariableID,
584 };
585
586 use maplit::btreemap;
587 use std::collections::BTreeSet;
588
589 fn empty_one_hot_constraint() -> crate::OneHotConstraint {
590 crate::OneHotConstraint {
591 variables: BTreeSet::new(),
592 stage: crate::OneHotCreatedData,
593 }
594 }
595
596 fn empty_sos1_constraint() -> crate::Sos1Constraint {
597 crate::Sos1Constraint {
598 variables: BTreeSet::new(),
599 stage: crate::Sos1CreatedData,
600 }
601 }
602
603 #[test]
604 fn add_decision_variable_preserves_duplicate_id_signal() {
605 let id = VariableID::from(7);
606 let mut instance = Instance::new(
607 Sense::Minimize,
608 Function::Zero,
609 btreemap! { id => DecisionVariable::binary() },
610 BTreeMap::new(),
611 )
612 .unwrap();
613
614 let error = instance
615 .add_decision_variable(
616 id,
617 DecisionVariable::binary(),
618 crate::DecisionVariableLabel::default(),
619 )
620 .unwrap_err();
621
622 assert!(matches!(
623 error.downcast_ref::<crate::DecisionVariableError>(),
624 Some(crate::DecisionVariableError::DuplicateID { id: duplicate }) if *duplicate == id
625 ));
626 assert_eq!(instance.decision_variables().len(), 1);
627 }
628
629 #[test]
630 fn parametric_add_decision_variable_preserves_substituted_duplicate_signal() {
631 let independent = VariableID::from(0);
632 let dependent = VariableID::from(7);
633 let instance = ParametricInstance::new(
634 Sense::Minimize,
635 Function::from(linear!(dependent.into_inner())),
636 btreemap! {
637 independent => DecisionVariable::binary(),
638 dependent => DecisionVariable::binary(),
639 },
640 ParameterTable::default(),
641 BTreeMap::new(),
642 )
643 .unwrap();
644 let mut instance = instance
645 .substitute_one(
646 dependent,
647 &Function::from(linear!(independent.into_inner())),
648 )
649 .unwrap();
650
651 let error = instance
652 .add_decision_variable(
653 dependent,
654 DecisionVariable::binary(),
655 crate::DecisionVariableLabel::default(),
656 )
657 .unwrap_err();
658
659 assert!(matches!(
660 error.downcast_ref::<crate::DecisionVariableError>(),
661 Some(crate::DecisionVariableError::DuplicateID { id }) if id == &dependent
662 ));
663 assert_eq!(instance.decision_variables().len(), 2);
664 }
665
666 #[test]
667 fn add_decision_variable_preserves_parameter_id_collision_signal() {
668 let parameter = VariableID::from(100);
669 let mut instance = ParametricInstance::new(
670 Sense::Minimize,
671 Function::Zero,
672 BTreeMap::new(),
673 ParameterTable::from_ids(BTreeSet::from([parameter])),
674 BTreeMap::new(),
675 )
676 .unwrap();
677
678 let error = instance
679 .add_decision_variable(
680 parameter,
681 DecisionVariable::binary(),
682 crate::DecisionVariableLabel::default(),
683 )
684 .unwrap_err();
685
686 assert!(matches!(
687 error.downcast_ref::<ParameterIDCollision>(),
688 Some(ParameterIDCollision { id }) if *id == parameter
689 ));
690 assert!(instance.decision_variables().is_empty());
691 }
692
693 #[test]
694 fn test_insert_constraint_success() {
695 let decision_variables = btreemap! {
697 VariableID::from(1) => DecisionVariable::binary(),
698 VariableID::from(2) => DecisionVariable::binary(),
699 };
700
701 let objective = linear!(1) + coeff!(1.0);
702
703 let mut instance = Instance::new(
704 Sense::Minimize,
705 objective.into(),
706 decision_variables,
707 BTreeMap::new(),
708 )
709 .unwrap();
710
711 let constraint = Constraint::equal_to_zero((linear!(1) + coeff!(2.0)).into());
713 let result = instance
714 .insert_constraint(ConstraintID::from(10), constraint.clone())
715 .unwrap();
716
717 assert!(result.is_none());
719 assert_eq!(instance.constraints().len(), 1);
720 assert_eq!(
721 instance.constraints().get(&ConstraintID::from(10)),
722 Some(&constraint)
723 );
724 }
725
726 #[test]
727 fn test_insert_constraint_replace_existing() {
728 let mut decision_variables = BTreeMap::new();
730 decision_variables.insert(VariableID::from(1), DecisionVariable::binary());
731 decision_variables.insert(VariableID::from(2), DecisionVariable::binary());
732
733 let objective = Function::Linear(Linear::single_term(
734 LinearMonomial::Variable(VariableID::from(1)),
735 coeff!(1.0),
736 ));
737
738 let mut constraints = BTreeMap::new();
739 let original_constraint = Constraint::equal_to_zero((linear!(1) + coeff!(1.0)).into());
740 constraints.insert(ConstraintID::from(5), original_constraint.clone());
741
742 let mut instance =
743 Instance::new(Sense::Minimize, objective, decision_variables, constraints).unwrap();
744
745 let new_constraint = Constraint::equal_to_zero((linear!(2) + coeff!(1.0)).into());
747 let result = instance
748 .insert_constraint(ConstraintID::from(5), new_constraint.clone())
749 .unwrap();
750
751 assert_eq!(result, Some(original_constraint));
753 assert_eq!(instance.constraints().len(), 1);
754 assert_eq!(
755 instance.constraints().get(&ConstraintID::from(5)),
756 Some(&new_constraint)
757 );
758 }
759
760 #[test]
761 fn test_insert_constraint_undefined_variable() {
762 let mut decision_variables = BTreeMap::new();
764 decision_variables.insert(VariableID::from(1), DecisionVariable::binary());
765 decision_variables.insert(VariableID::from(2), DecisionVariable::binary());
766
767 let objective = Function::Linear(Linear::single_term(
768 LinearMonomial::Variable(VariableID::from(1)),
769 coeff!(1.0),
770 ));
771
772 let mut instance = Instance::new(
773 Sense::Minimize,
774 objective,
775 decision_variables,
776 BTreeMap::new(),
777 )
778 .unwrap();
779
780 let constraint = Constraint::equal_to_zero((linear!(999) + coeff!(1.0)).into());
782 let result = instance.insert_constraint(ConstraintID::from(1), constraint);
783
784 assert!(result.is_err());
786 let err = result.unwrap_err();
787 assert_eq!(
788 err.to_string(),
789 "Undefined variable ID is used: VariableID(999)"
790 );
791 assert_eq!(instance.constraints().len(), 0);
793 }
794
795 #[test]
796 fn test_insert_constraint_multiple_operations() {
797 let mut decision_variables = BTreeMap::new();
799 decision_variables.insert(VariableID::from(1), DecisionVariable::binary());
800 decision_variables.insert(VariableID::from(2), DecisionVariable::binary());
801 decision_variables.insert(VariableID::from(3), DecisionVariable::binary());
802
803 let objective = Function::Linear(Linear::single_term(
804 LinearMonomial::Variable(VariableID::from(1)),
805 coeff!(1.0),
806 ));
807
808 let mut instance = Instance::new(
809 Sense::Minimize,
810 objective,
811 decision_variables,
812 BTreeMap::new(),
813 )
814 .unwrap();
815
816 let constraint1 = Constraint::equal_to_zero((linear!(1) + coeff!(1.0)).into());
818 let constraint2 = Constraint::equal_to_zero((linear!(2) + coeff!(1.0)).into());
819 let constraint3 = Constraint::equal_to_zero((linear!(3) + coeff!(1.0)).into());
820
821 assert!(instance
822 .insert_constraint(ConstraintID::from(1), constraint1.clone())
823 .unwrap()
824 .is_none());
825 assert!(instance
826 .insert_constraint(ConstraintID::from(2), constraint2.clone())
827 .unwrap()
828 .is_none());
829 assert!(instance
830 .insert_constraint(ConstraintID::from(3), constraint3.clone())
831 .unwrap()
832 .is_none());
833 assert_eq!(instance.constraints().len(), 3);
834
835 let new_constraint2 = Constraint::equal_to_zero((linear!(1) + coeff!(1.0)).into());
837 let replaced = instance
838 .insert_constraint(ConstraintID::from(2), new_constraint2.clone())
839 .unwrap();
840 assert_eq!(replaced, Some(constraint2));
841 assert_eq!(instance.constraints().len(), 3);
842 assert_eq!(
843 instance.constraints().get(&ConstraintID::from(2)),
844 Some(&new_constraint2)
845 );
846 }
847
848 #[test]
849 fn test_insert_constraint_with_dependency_key() {
850 let decision_variables = btreemap! {
852 VariableID::from(1) => DecisionVariable::binary(),
853 VariableID::from(2) => DecisionVariable::binary(),
854 VariableID::from(3) => DecisionVariable::binary(),
855 };
856 let objective = linear!(1) + coeff!(1.0);
857 let mut instance = Instance::new(
858 Sense::Minimize,
859 objective.into(),
860 decision_variables,
861 BTreeMap::new(),
862 )
863 .unwrap();
864
865 instance.decision_variable_dependency = assign! {
867 2 <- linear!(1) + coeff!(1.0)
868 };
869
870 let constraint = Constraint::equal_to_zero((linear!(2) + coeff!(1.0)).into());
872 let result = instance.insert_constraint(ConstraintID::from(1), constraint);
873 assert_eq!(
874 result.unwrap_err().to_string(),
875 "Dependent variable cannot be used in objectives or constraints: VariableID(2)"
876 );
877 assert_eq!(instance.constraints().len(), 0);
879 }
880
881 #[test]
882 fn test_add_constraint_rejects_fixed_variable() {
883 let decision_variables = btreemap! {
886 VariableID::from(1) => DecisionVariable::binary(),
887 VariableID::from(2) => DecisionVariable::binary(),
888 };
889
890 let objective = linear!(1) + coeff!(1.0);
891 let mut instance = Instance::builder()
892 .sense(Sense::Minimize)
893 .objective(objective.into())
894 .decision_variables(decision_variables)
895 .fixed_decision_variable_values(btreemap! {
896 VariableID::from(2) => 0.0,
897 })
898 .constraints(BTreeMap::new())
899 .build()
900 .unwrap();
901
902 let bad = crate::Constraint::equal_to_zero((linear!(2) + coeff!(1.0)).into());
903 let err = instance
904 .add_constraint(bad, crate::ConstraintContext::default())
905 .unwrap_err();
906 assert!(
907 err.to_string().contains("Fixed variable") && err.to_string().contains("VariableID(2)"),
908 "unexpected error: {err}"
909 );
910 assert!(instance.constraints().is_empty());
911 }
912
913 #[test]
914 fn test_add_one_hot_constraint_rejects_empty_variable_set() {
915 let mut instance = Instance::new(
916 Sense::Minimize,
917 Function::Zero,
918 BTreeMap::new(),
919 BTreeMap::new(),
920 )
921 .unwrap();
922
923 let err = instance
924 .add_one_hot_constraint(
925 empty_one_hot_constraint(),
926 crate::ConstraintContext::default(),
927 )
928 .unwrap_err();
929
930 assert!(matches!(
931 err.downcast_ref::<crate::OneHotConstraintError>(),
932 Some(crate::OneHotConstraintError::EmptyVariables)
933 ));
934 assert!(
935 err.to_string().contains("at least one variable"),
936 "unexpected error: {err}"
937 );
938 assert!(instance.one_hot_constraints().is_empty());
939 }
940
941 #[test]
942 fn test_parametric_add_one_hot_constraint_rejects_empty_variable_set() {
943 let mut instance = ParametricInstance::new(
944 Sense::Minimize,
945 Function::Zero,
946 BTreeMap::new(),
947 crate::ParameterTable::default(),
948 BTreeMap::new(),
949 )
950 .unwrap();
951
952 let err = instance
953 .add_one_hot_constraint(
954 empty_one_hot_constraint(),
955 crate::ConstraintContext::default(),
956 )
957 .unwrap_err();
958
959 assert!(matches!(
960 err.downcast_ref::<crate::OneHotConstraintError>(),
961 Some(crate::OneHotConstraintError::EmptyVariables)
962 ));
963 assert!(
964 err.to_string().contains("at least one variable"),
965 "unexpected error: {err}"
966 );
967 assert!(instance.one_hot_constraints().is_empty());
968 }
969
970 #[test]
971 fn test_add_sos1_constraint_rejects_empty_variable_set() {
972 let mut instance = Instance::new(
973 Sense::Minimize,
974 Function::Zero,
975 BTreeMap::new(),
976 BTreeMap::new(),
977 )
978 .unwrap();
979
980 let err = instance
981 .add_sos1_constraint(empty_sos1_constraint(), crate::ConstraintContext::default())
982 .unwrap_err();
983
984 assert!(matches!(
985 err.downcast_ref::<crate::Sos1ConstraintError>(),
986 Some(crate::Sos1ConstraintError::EmptyVariables)
987 ));
988 assert!(instance.sos1_constraints().is_empty());
989 }
990
991 #[test]
992 fn test_parametric_add_sos1_constraint_rejects_empty_variable_set() {
993 let mut instance = ParametricInstance::new(
994 Sense::Minimize,
995 Function::Zero,
996 BTreeMap::new(),
997 crate::ParameterTable::default(),
998 BTreeMap::new(),
999 )
1000 .unwrap();
1001
1002 let err = instance
1003 .add_sos1_constraint(empty_sos1_constraint(), crate::ConstraintContext::default())
1004 .unwrap_err();
1005
1006 assert!(matches!(
1007 err.downcast_ref::<crate::Sos1ConstraintError>(),
1008 Some(crate::Sos1ConstraintError::EmptyVariables)
1009 ));
1010 assert!(instance.sos1_constraints().is_empty());
1011 }
1012
1013 #[test]
1014 fn test_set_objective_with_dependency_key() {
1015 let decision_variables = btreemap! {
1017 VariableID::from(1) => DecisionVariable::binary(),
1018 VariableID::from(2) => DecisionVariable::binary(),
1019 };
1020 let objective = linear!(1) + coeff!(1.0);
1021 let mut instance = Instance::new(
1022 Sense::Minimize,
1023 objective.into(),
1024 decision_variables,
1025 BTreeMap::new(),
1026 )
1027 .unwrap();
1028
1029 instance.decision_variable_dependency = assign! {
1031 2 <- linear!(1) + coeff!(1.0)
1032 };
1033
1034 let new_objective = linear!(2) + coeff!(1.0);
1036 let result = instance.set_objective(new_objective.into());
1037
1038 assert_eq!(
1040 result.unwrap_err().to_string(),
1041 "Dependent variable cannot be used in objectives or constraints: VariableID(2)"
1042 );
1043 assert_eq!(instance.objective, Function::from(linear!(1) + coeff!(1.0)));
1045 }
1046
1047 #[test]
1048 fn test_insert_constraint_replace_removed_constraint() {
1049 let decision_variables = btreemap! {
1051 VariableID::from(1) => DecisionVariable::binary(),
1052 VariableID::from(2) => DecisionVariable::binary(),
1053 };
1054
1055 let objective = (linear!(1) + coeff!(1.0)).into();
1056 let constraints = btreemap! {
1057 ConstraintID::from(1) => Constraint::equal_to_zero((linear!(1) + coeff!(1.0)).into(),
1058 ),
1059 ConstraintID::from(2) => Constraint::equal_to_zero((linear!(2) + coeff!(2.0)).into(),
1060 ),
1061 };
1062
1063 let mut instance =
1064 Instance::new(Sense::Minimize, objective, decision_variables, constraints).unwrap();
1065 instance
1066 .relax_constraint(ConstraintID::from(2), "test".to_string(), [])
1067 .unwrap();
1068
1069 assert_eq!(instance.constraints().len(), 1);
1071 assert_eq!(instance.removed_constraints().len(), 1);
1072
1073 let new_constraint = Constraint::equal_to_zero(
1075 ((linear!(1) + linear!(2)).unwrap() + coeff!(3.0))
1076 .unwrap()
1077 .into(),
1078 );
1079 let result = instance
1080 .insert_constraint(ConstraintID::from(2), new_constraint.clone())
1081 .unwrap();
1082
1083 assert_eq!(
1085 result,
1086 Some(Constraint::equal_to_zero((linear!(2) + coeff!(2.0)).into(),))
1087 );
1088
1089 assert_eq!(instance.constraints().len(), 1);
1090 assert_eq!(instance.removed_constraints().len(), 1);
1091 let (removed, _reason) = instance
1092 .removed_constraints()
1093 .get(&ConstraintID::from(2))
1094 .unwrap();
1095 assert_eq!(removed.equality, new_constraint.equality);
1096 assert_eq!(removed.stage.function, new_constraint.stage.function);
1097 }
1098
1099 #[test]
1100 fn test_insert_constraints_bulk() {
1101 let decision_variables = btreemap! {
1103 VariableID::from(1) => DecisionVariable::binary(),
1104 VariableID::from(2) => DecisionVariable::binary(),
1105 VariableID::from(3) => DecisionVariable::binary(),
1106 };
1107 let objective = linear!(1) + coeff!(1.0);
1108 let mut instance = Instance::new(
1109 Sense::Minimize,
1110 objective.into(),
1111 decision_variables,
1112 BTreeMap::new(),
1113 )
1114 .unwrap();
1115
1116 let constraints = vec![
1118 (
1119 ConstraintID::from(1),
1120 Constraint::equal_to_zero((linear!(1) + coeff!(1.0)).into()),
1121 ),
1122 (
1123 ConstraintID::from(2),
1124 Constraint::equal_to_zero((linear!(2) + coeff!(2.0)).into()),
1125 ),
1126 (
1127 ConstraintID::from(3),
1128 Constraint::equal_to_zero((linear!(3) + coeff!(3.0)).into()),
1129 ),
1130 ];
1131
1132 let replaced = instance.insert_constraints(constraints.clone()).unwrap();
1133
1134 assert!(replaced.is_empty());
1136 assert_eq!(instance.constraints().len(), 3);
1137
1138 for (id, constraint) in &constraints {
1140 assert_eq!(instance.constraints().get(id), Some(constraint));
1141 }
1142 }
1143
1144 #[test]
1145 fn test_insert_constraints_bulk_with_undefined_variable() {
1146 let decision_variables = btreemap! {
1148 VariableID::from(1) => DecisionVariable::binary(),
1149 VariableID::from(2) => DecisionVariable::binary(),
1150 };
1151 let objective = linear!(1) + coeff!(1.0);
1152 let mut instance = Instance::new(
1153 Sense::Minimize,
1154 objective.into(),
1155 decision_variables,
1156 BTreeMap::new(),
1157 )
1158 .unwrap();
1159
1160 let constraints = vec![
1162 (
1163 ConstraintID::from(1),
1164 Constraint::equal_to_zero((linear!(1) + coeff!(1.0)).into()),
1165 ),
1166 (
1167 ConstraintID::from(2),
1168 Constraint::equal_to_zero((linear!(999) + coeff!(2.0)).into()),
1169 ),
1170 (
1171 ConstraintID::from(3),
1172 Constraint::equal_to_zero((linear!(2) + coeff!(3.0)).into()),
1173 ),
1174 ];
1175
1176 let result = instance.insert_constraints(constraints);
1177
1178 assert!(result.is_err());
1180 assert_eq!(
1181 result.unwrap_err().to_string(),
1182 "Undefined variable ID is used: VariableID(999)"
1183 );
1184 assert_eq!(instance.constraints().len(), 0);
1186 }
1187
1188 #[test]
1189 fn test_insert_constraints_bulk_replace_existing() {
1190 let decision_variables = btreemap! {
1192 VariableID::from(1) => DecisionVariable::binary(),
1193 VariableID::from(2) => DecisionVariable::binary(),
1194 };
1195 let objective = linear!(1) + coeff!(1.0);
1196 let constraints = btreemap! {
1197 ConstraintID::from(1) => Constraint::equal_to_zero((linear!(1) + coeff!(1.0)).into(),
1198 ),
1199 ConstraintID::from(2) => Constraint::equal_to_zero((linear!(2) + coeff!(2.0)).into(),
1200 ),
1201 };
1202 let mut instance = Instance::new(
1203 Sense::Minimize,
1204 objective.into(),
1205 decision_variables,
1206 constraints,
1207 )
1208 .unwrap();
1209
1210 let new_constraints = vec![
1212 (
1213 ConstraintID::from(1),
1214 Constraint::equal_to_zero((linear!(2) + coeff!(10.0)).into()),
1215 ),
1216 (
1217 ConstraintID::from(3),
1218 Constraint::equal_to_zero((linear!(1) + coeff!(3.0)).into()),
1219 ),
1220 ];
1221
1222 let replaced = instance
1223 .insert_constraints(new_constraints.clone())
1224 .unwrap();
1225
1226 assert_eq!(replaced.len(), 1);
1228 assert!(replaced.contains_key(&ConstraintID::from(1)));
1229 assert_eq!(instance.constraints().len(), 3);
1230 }
1231
1232 #[test]
1233 fn test_insert_constraints_bulk_replace_removed() {
1234 let decision_variables = btreemap! {
1236 VariableID::from(1) => DecisionVariable::binary(),
1237 VariableID::from(2) => DecisionVariable::binary(),
1238 };
1239 let objective = linear!(1) + coeff!(1.0);
1240 let constraints = btreemap! {
1241 ConstraintID::from(1) => Constraint::equal_to_zero((linear!(1) + coeff!(1.0)).into(),
1242 ),
1243 };
1244 let mut instance = Instance::new(
1245 Sense::Minimize,
1246 objective.into(),
1247 decision_variables,
1248 constraints,
1249 )
1250 .unwrap();
1251
1252 instance
1254 .relax_constraint(ConstraintID::from(1), "test".to_string(), [])
1255 .unwrap();
1256 assert_eq!(instance.constraints().len(), 0);
1257 assert_eq!(instance.removed_constraints().len(), 1);
1258
1259 let new_constraints = vec![(
1261 ConstraintID::from(1),
1262 Constraint::equal_to_zero((linear!(2) + coeff!(10.0)).into()),
1263 )];
1264
1265 let replaced = instance.insert_constraints(new_constraints).unwrap();
1266
1267 assert_eq!(replaced.len(), 1);
1269 assert!(replaced.contains_key(&ConstraintID::from(1)));
1270 assert_eq!(instance.removed_constraints().len(), 1);
1272 }
1273
1274 #[test]
1275 fn test_insert_constraints_bulk_with_dependent_variable() {
1276 let decision_variables = btreemap! {
1278 VariableID::from(1) => DecisionVariable::binary(),
1279 VariableID::from(2) => DecisionVariable::binary(),
1280 VariableID::from(3) => DecisionVariable::binary(),
1281 };
1282 let objective = linear!(1) + coeff!(1.0);
1283 let mut instance = Instance::new(
1284 Sense::Minimize,
1285 objective.into(),
1286 decision_variables,
1287 BTreeMap::new(),
1288 )
1289 .unwrap();
1290
1291 instance.decision_variable_dependency = assign! {
1293 2 <- linear!(1) + coeff!(1.0)
1294 };
1295
1296 let constraints = vec![
1298 (
1299 ConstraintID::from(1),
1300 Constraint::equal_to_zero((linear!(1) + coeff!(1.0)).into()),
1301 ),
1302 (
1303 ConstraintID::from(2),
1304 Constraint::equal_to_zero((linear!(2) + coeff!(2.0)).into()),
1305 ),
1306 ];
1307
1308 let result = instance.insert_constraints(constraints);
1309
1310 assert!(result.is_err());
1312 assert_eq!(
1313 result.unwrap_err().to_string(),
1314 "Dependent variable cannot be used in objectives or constraints: VariableID(2)"
1315 );
1316 assert_eq!(instance.constraints().len(), 0);
1318 }
1319
1320 #[test]
1321 fn test_next_constraint_id() {
1322 let decision_variables = btreemap! {
1324 VariableID::from(1) => DecisionVariable::binary(),
1325 };
1326 let objective = (linear!(1) + coeff!(1.0)).into();
1327 let instance = Instance::new(
1328 Sense::Minimize,
1329 objective,
1330 decision_variables,
1331 BTreeMap::new(),
1332 )
1333 .unwrap();
1334 assert_eq!(instance.next_constraint_id(), ConstraintID::from(0));
1335
1336 let decision_variables = btreemap! {
1338 VariableID::from(1) => DecisionVariable::binary(),
1339 };
1340 let objective = (linear!(1) + coeff!(1.0)).into();
1341 let constraints = btreemap! {
1342 ConstraintID::from(3) => Constraint::equal_to_zero((linear!(1) + coeff!(1.0)).into(),
1343 ),
1344 ConstraintID::from(15) => Constraint::equal_to_zero((linear!(1) + coeff!(2.0)).into(),
1345 ),
1346 };
1347 let mut instance =
1348 Instance::new(Sense::Minimize, objective, decision_variables, constraints).unwrap();
1349 instance
1350 .relax_constraint(ConstraintID::from(15), "test".to_string(), [])
1351 .unwrap();
1352
1353 assert_eq!(instance.next_constraint_id(), ConstraintID::from(16));
1355 }
1356}