diff --git a/symbolic/constraint.go b/symbolic/constraint.go index e375fd1..2caa3c2 100644 --- a/symbolic/constraint.go +++ b/symbolic/constraint.go @@ -15,6 +15,18 @@ type Constraint interface { IsLinear() bool Substitute(vIn Variable, seIn ScalarExpression) Constraint SubstituteAccordingTo(subMap map[Variable]Expression) Constraint + + // Variables + // Returns a slice of all the variables in the constraint. + Variables() []Variable + + // ImpliesThisIsAlsoSatisfied + // Returns true if this constraint implies that the other constraint is also satisfied. + ImpliesThisIsAlsoSatisfied(other Constraint) bool + + // AsSimplifiedConstraint + // Simplifies the constraint by moving all variables to the left hand side and the constants to the right. + AsSimplifiedConstraint() Constraint } func IsConstraint(c interface{}) bool { @@ -36,3 +48,38 @@ func IsConstraint(c interface{}) bool { // Return false, if the constraint is not a scalar or vector constraint. return false } + +/* +Variables +Description: + + Returns a slice of all the variables in the constraint. +*/ +func VariablesInThisConstraint(c Constraint) []Variable { + // Setup + varsMap := make(map[Variable]bool) + + // Input check + err := c.Check() + if err != nil { + panic(err) + } + + // Get variables from the left hand side + for _, v := range c.Left().Variables() { + varsMap[v] = true + } + + // Get variables from the right hand side + for _, v := range c.Right().Variables() { + varsMap[v] = true + } + + // Convert the map to a slice + vars := make([]Variable, 0, len(varsMap)) + for v := range varsMap { + vars = append(vars, v) + } + + return vars +} diff --git a/symbolic/matrix_constraint.go b/symbolic/matrix_constraint.go index 0715ef9..c906382 100644 --- a/symbolic/matrix_constraint.go +++ b/symbolic/matrix_constraint.go @@ -191,3 +191,84 @@ func (mc MatrixConstraint) SubstituteAccordingTo(subMap map[Variable]Expression) return MatrixConstraint{newLHS, newRHS, mc.Sense} } + +/* +AsSimplifiedConstraint +Description: + + Simplifies the constraint by moving all variables to the left hand side and the constants to the right. +*/ +func (mc MatrixConstraint) AsSimplifiedConstraint() Constraint { + // Create Left Hand side of all of the expressions + var newLHS Expression = mc.LeftHandSide.Minus(mc.LeftHandSide.Constant()) + newLHS = newLHS.Minus( + mc.RightHandSide.Minus(mc.RightHandSide.Constant()), + ) + + // Create Right Hand Side of only constants + var newRHS Expression = DenseToKMatrix(mc.RightHandSide.Constant()).Minus( + mc.LeftHandSide.Constant(), + ) + + // Return new constraint + return MatrixConstraint{ + LeftHandSide: newLHS.(MatrixExpression), + RightHandSide: newRHS.(MatrixExpression), + Sense: mc.Sense, + } +} + +/* +Variables +Description: + + Returns a slice of all the variables in the constraint. +*/ +func (mc MatrixConstraint) Variables() []Variable { + return VariablesInThisConstraint(mc) +} + +/* +ImpliesThisIsAlsoSatisfied +Description: + + Returns true if this constraint implies that the other constraint is also satisfied. +*/ +func (mc MatrixConstraint) ImpliesThisIsAlsoSatisfied(other Constraint) bool { + // Input Processing + err := mc.Check() + if err != nil { + panic(err) + } + + err = other.Check() + if err != nil { + panic(err) + } + + // Implication Avenues + switch otherC := other.(type) { + case ScalarConstraint: + // If the other constraint is a scalar constraint, + // then it can only be implied if: + // - one of the elements of the matrix constraint implies the scalar constraint. + for i := 0; i < mc.Dims()[0]; i++ { + for j := 0; j < mc.Dims()[1]; j++ { + if mc.At(i, j).ImpliesThisIsAlsoSatisfied(otherC) { + return true + } + } + } + case VectorConstraint, MatrixConstraint: + // TODO: Implement more advanced implication checks. + return false + default: + // Other types of constraints are not currently supported. + panic( + fmt.Errorf("implication checking between MatrixConstraint and %T is not currently supported", other), + ) + } + + // If no avenues for implication were found, return false. + return false +} diff --git a/symbolic/scalar_constraint.go b/symbolic/scalar_constraint.go index 18cffbb..70f860e 100644 --- a/symbolic/scalar_constraint.go +++ b/symbolic/scalar_constraint.go @@ -1,6 +1,8 @@ package symbolic import ( + "fmt" + "github.com/MatProGo-dev/SymbolicMath.go/smErrors" "gonum.org/v1/gonum/mat" ) @@ -281,3 +283,165 @@ func (sc ScalarConstraint) String() string { // Create the string representation return sc.LeftHandSide.String() + " " + sc.Sense.String() + " " + sc.RightHandSide.String() } + +/* +Simplify +Description: + + Simplifies the constraint by moving all variables to the left hand side and the constants to the right. +*/ +func (sc ScalarConstraint) AsSimplifiedConstraint() Constraint { + return sc.Simplify() +} + +func (sc ScalarConstraint) Variables() []Variable { + return VariablesInThisConstraint(sc) +} + +func (sc ScalarConstraint) ScaleBy(factor float64) Constraint { + // Check that the constraint is well formed. + err := sc.Check() + if err != nil { + panic(err) + } + + // Scale the left hand side + newLHS := sc.LeftHandSide.Multiply(factor).(ScalarExpression) + + // Scale the right hand side + newRHS := sc.RightHandSide.Multiply(factor).(ScalarExpression) + + // If the factor is negative, then flip the sense of the constraint + newSense := sc.Sense + if factor < 0 { + if sc.Sense == SenseLessThanEqual { + newSense = SenseGreaterThanEqual + } else if sc.Sense == SenseGreaterThanEqual { + newSense = SenseLessThanEqual + } + } + + // Return the new constraint + return ScalarConstraint{ + LeftHandSide: newLHS, + RightHandSide: newRHS, + Sense: newSense, + } +} + +/* +ImpliesThisIsAlsoSatisfied +Description: + + Returns true if this constraint implies that the other constraint is also satisfied. +*/ +func (sc ScalarConstraint) ImpliesThisIsAlsoSatisfied(other Constraint) bool { + // Check that the constraint is well formed. + err := sc.Check() + if err != nil { + panic(err) + } + + // Check that the other constraint is well formed. + err = other.Check() + if err != nil { + panic(err) + } + + // Simplify both constraints + sc = sc.Simplify() + + switch otherC := other.(type) { + case ScalarConstraint: + otherC = otherC.Simplify() + + // Naive implication check: + // 1. Both constraints contain only 1 variable AND it is the same variable. Then, simply check the bounds. + containsOneVar := len(sc.Variables()) == 1 && len(otherC.Variables()) == 1 + scAndOtherShareSameVar := len(UnionOfVariables(sc.Variables(), otherC.Variables())) == 1 + + if containsOneVar && scAndOtherShareSameVar { + // Get the coefficient of the single variable + scCoeffVector := sc.LeftHandSide.LinearCoeff(sc.Variables()) + scCoeff := scCoeffVector.AtVec(0) + otherCCoeffVector := otherC.LeftHandSide.LinearCoeff(otherC.Variables()) + otherCCoeff := otherCCoeffVector.AtVec(0) + + // If the coefficient of scCoeff is < 0, + // then flip the signs of both sides of the constraint + if scCoeff < 0 { + sc = sc.ScaleBy(-1).(ScalarConstraint) + } + + if otherCCoeff < 0 { + otherC = otherC.ScaleBy(-1).(ScalarConstraint) + } + + // The implication holds if all of the following are true: + // 1. The sense of sc and otherC are either the same (or one is equality) + // 2. The bounds of the constraint with the LessThanEqual or GreaterThanEqual sense are within the bounds of the other constraint. + sensesAreCompatible := sc.Sense == otherC.Sense || + sc.Sense == SenseEqual || + otherC.Sense == SenseEqual + + if !sensesAreCompatible { + return false + } + + switch sc.Sense { + case SenseLessThanEqual: + // Check the senses of otherC + switch otherC.Sense { + case SenseLessThanEqual: + // Both are <= + // Then the implication holds if the upper bound of sc is <= the upper bound of otherC + return sc.RightHandSide.Constant() <= otherC.RightHandSide.Constant() + default: + // sc is <= and otherC is either >= or == + // Then the implication holds if the upper bound of sc is <= the lower bound of otherC + return false + } + case SenseGreaterThanEqual: + // Check the senses of otherC + switch otherC.Sense { + case SenseGreaterThanEqual: + // Both are >= + // Then the implication holds if the lower bound of sc is >= the lower bound of otherC + return sc.RightHandSide.Constant() >= otherC.RightHandSide.Constant() + default: + // sc is >= and otherC is either <= or == + // Then the implication holds if the lower bound of sc is >= the upper bound of otherC + return false + } + case SenseEqual: + // Check the senses of otherC + switch otherC.Sense { + case SenseEqual: + // Both are == + // Then the implication holds if the bounds are equal + return sc.RightHandSide.Constant() == otherC.RightHandSide.Constant() + case SenseLessThanEqual: + // sc is == and otherC is <= + // Then the implication holds if the bound of sc is <= the upper bound of otherC + return sc.RightHandSide.Constant() <= otherC.RightHandSide.Constant() + case SenseGreaterThanEqual: + // sc is == and otherC is >= + // Then the implication holds if the bound of sc is >= the lower bound of otherC + return sc.RightHandSide.Constant() >= otherC.RightHandSide.Constant() + } + default: + panic("unreachable code reached in ScalarConstraint.ImpliesThisIsAlsoSatisfied") + } + } + case VectorConstraint, MatrixConstraint: + // TODO: Implement more advanced implication checks. + return false + default: + // Other types of constraints are not currently supported. + panic( + fmt.Errorf("implication checking between ScalarConstraint and %T is not currently supported", other), + ) + } + + return false +} diff --git a/symbolic/variable.go b/symbolic/variable.go index 5c888f0..9aad657 100644 --- a/symbolic/variable.go +++ b/symbolic/variable.go @@ -89,6 +89,8 @@ func (v Variable) Plus(rightIn interface{}) Expression { switch right := rightIn.(type) { case float64: return v.Plus(K(right)) + case int: + return v.Plus(K(float64(right))) case K: return Polynomial{ Monomials: []Monomial{ @@ -316,6 +318,8 @@ func (v Variable) Multiply(rightIn interface{}) Expression { switch right := rightIn.(type) { case float64: return v.Multiply(K(right)) + case int: + return v.Multiply(K(float64(right))) case K: // Create a new monomial monomialOut := Monomial{ @@ -650,3 +654,11 @@ func (v Variable) At(ii, jj int) ScalarExpression { // Algorithm return v } + +func UnionOfVariables(varSlices ...[]Variable) []Variable { + var allVars []Variable + for _, varSlice := range varSlices { + allVars = append(allVars, varSlice...) + } + return UniqueVars(allVars) +} diff --git a/symbolic/vector_constraint.go b/symbolic/vector_constraint.go index 1bd8bea..57911ef 100644 --- a/symbolic/vector_constraint.go +++ b/symbolic/vector_constraint.go @@ -1,6 +1,8 @@ package symbolic import ( + "fmt" + "github.com/MatProGo-dev/SymbolicMath.go/smErrors" "gonum.org/v1/gonum/mat" ) @@ -316,3 +318,87 @@ func (vc VectorConstraint) Len() int { // Return the length of the vector constraint. return vc.LeftHandSide.Len() } + +/* +AsSimplifiedConstraint +Description: + + Simplifies the constraint by moving all variables to the left hand side and the constants to the right. +*/ +func (vc VectorConstraint) AsSimplifiedConstraint() Constraint { + // Input Checking + err := vc.Check() + if err != nil { + panic(err) + } + + // Create Left Hand side of all of the expressions + var newLHS Expression = vc.LeftHandSide.Minus(vc.LeftHandSide.Constant()) + newLHS = newLHS.Minus( + vc.RightHandSide.Minus(vc.RightHandSide.Constant()), + ) + + // Create Right Hand Side of only constants + var newRHS Expression = VecDenseToKVector(vc.RightHandSide.Constant()).Minus( + vc.LeftHandSide.Constant(), + ) + + // Return new constraint + return VectorConstraint{ + LeftHandSide: newLHS.(VectorExpression), + RightHandSide: newRHS.(VectorExpression), + Sense: vc.Sense, + } +} + +/* +Variables +Description: + + Returns a slice of all the variables in the constraint. +*/ +func (vc VectorConstraint) Variables() []Variable { + return VariablesInThisConstraint(vc) +} + +/* +ImpliesThisIsAlsoSatisfied +Description: + + Returns true if this constraint implies that the other constraint is also satisfied. +*/ +func (vc VectorConstraint) ImpliesThisIsAlsoSatisfied(other Constraint) bool { + // Input Processing + err := vc.Check() + if err != nil { + panic(err) + } + + // Check the other constraint + err = other.Check() + if err != nil { + panic(err) + } + + switch otherC := other.(type) { + case ScalarConstraint: + // Continue + // Naive implication check: + // 1. One of the scalar constraints produces the correct implications. + for i := 0; i < vc.Len(); i++ { + if vc.AtVec(i).ImpliesThisIsAlsoSatisfied(otherC) { + return true + } + } + case VectorConstraint, MatrixConstraint: + // TODO: Implement more advanced implication checks. + return false + default: + // Other types of constraints are not currently supported. + panic( + fmt.Errorf("implication checking between VectorConstraint and %T is not currently supported", other), + ) + } + + return false +} diff --git a/testing/symbolic/constraint_test.go b/testing/symbolic/constraint_test.go index 2fd82ea..359d0db 100644 --- a/testing/symbolic/constraint_test.go +++ b/testing/symbolic/constraint_test.go @@ -7,8 +7,9 @@ Description: */ import ( - "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "testing" + + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" ) /* @@ -174,3 +175,44 @@ func TestConstraint_IsConstraint7(t *testing.T) { ) } } + +/* +TestConstraint_VariablesInThisConstraint1 +Description: + + Verifies that the VariablesInThisConstraint function works as expected. + We verify that the method properly returns 5 unique variables when + there are 3 variables on the left hand side and 3 variables on the right hand side, + but one of the variables is shared between the two sides. +*/ +func TestConstraint_VariablesInThisConstraint1(t *testing.T) { + // Constants + x1 := symbolic.NewVariable() + x2 := symbolic.NewVariable() + x3 := symbolic.NewVariable() + x4 := symbolic.NewVariable() + x5 := symbolic.NewVariable() + + m1 := symbolic.Monomial{ + Coefficient: 1, + Exponents: []int{1, 2, 1}, + VariableFactors: []symbolic.Variable{x1, x2, x3}, + } + + m2 := symbolic.Monomial{ + Coefficient: 1, + Exponents: []int{3, 1, 1}, + VariableFactors: []symbolic.Variable{x3, x4, x5}, + } + + sc := symbolic.ScalarConstraint{m1, m2, symbolic.SenseEqual} + + // Test + vars := symbolic.VariablesInThisConstraint(sc) + if len(vars) != 5 { + t.Errorf( + "Expected VariablesInThisConstraint to return 5 unique variables; received %d", + len(vars), + ) + } +} diff --git a/testing/symbolic/matrix_constraint_test.go b/testing/symbolic/matrix_constraint_test.go index b939c26..687a65c 100644 --- a/testing/symbolic/matrix_constraint_test.go +++ b/testing/symbolic/matrix_constraint_test.go @@ -641,3 +641,235 @@ func TestMatrixConstraint_SubstituteAccordingTo2(t *testing.T) { ) } } + +/* +TestMatrixConstraint_AsSimplifiedConstraint1 +Description: + + This test verifies that AsSimplifiedConstraint() properly panics + whenever a malformed MatrixConstraint is used to call it. +*/ +func TestMatrixConstraint_AsSimplifiedConstraint1(t *testing.T) { + // Create constraint + left := symbolic.MonomialMatrix{} + right := symbolic.DenseToKMatrix(symbolic.ZerosMatrix(3, 4)) + mc := symbolic.MatrixConstraint{left, right, symbolic.SenseLessThanEqual} + + expectedError := left.Check() + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "Expected mc.SubstituteAccordingTo() to panic; did not panic", + ) + } + + // Check that the error is the expected error + err, ok := r.(error) + if !ok { + t.Errorf( + "Expected mc.SubstituteAccordingTo() to panic with type error; received %T", + r, + ) + } + + if err.Error() != expectedError.Error() { + t.Errorf( + "Expected mc.SubstituteAccordingTo() to panic with error \"%v\"; received \"%v\"", + expectedError, + err, + ) + } + + }() + + // Call AsSimplifiedConstraint() + mc.AsSimplifiedConstraint() + + t.Errorf( + "Expected mc.AsSimplifiedConstraint() to panic; did not panic", + ) + +} + +/* +TestMatrixConstraint_Variables1 +Description: + + Verifies that the Variables() method works as expected. + We verify that the method properly returns 9 unique variables when + we create a constraint between a 3x3 matrix of variables and a + 3x3 matrix of constants. +*/ +func TestMatrixConstraint_Variables1(t *testing.T) { + // Constants + vm1 := symbolic.NewVariableMatrix(3, 3) + km2 := symbolic.DenseToKMatrix(symbolic.OnesMatrix(3, 3)) + + mConstr := symbolic.MatrixConstraint{vm1, km2, symbolic.SenseLessThanEqual} + + // Test + vars := mConstr.Variables() + + if len(vars) != 9 { + t.Errorf( + "Expected len(vars) to be 9; received %v", + len(vars), + ) + } + // Check that each variable is unique + varsMap := make(map[symbolic.Variable]bool) + for _, v := range vars { + if _, ok := varsMap[v]; ok { + t.Errorf( + "Expected all variables to be unique; received duplicate variable %v", + v, + ) + } + varsMap[v] = true + } +} + +/* +TestMatrixConstraint_ImpliesThisIsAlsoSatisfied1 +Description: + + Verifies that the ImpliesThisIsAlsoSatisfied method works as expected + when the input constraint is a scalar constraint that matches one of + the constraints in the original matrix constraint (i.e., the scalar constraint + is just the (0,1)-th constraint in the original matrix constraint). +*/ +func TestMatrixConstraint_ImpliesThisIsAlsoSatisfied1(t *testing.T) { + // Constants + vm1 := symbolic.NewVariableMatrix(3, 3) + km2 := symbolic.DenseToKMatrix(symbolic.OnesMatrix(3, 3)) + + mConstr := symbolic.MatrixConstraint{vm1, km2, symbolic.SenseLessThanEqual} + + // Extract the (0,1)-th constraint from mConstr + scalarConstraint := mConstr.At(0, 1) + + // Test + if !mConstr.ImpliesThisIsAlsoSatisfied(scalarConstraint) { + t.Errorf( + "Expected mConstr.ImpliesThisIsAlsoSatisfied(scalarConstraint) to be true; received false", + ) + } +} + +/* +TestMatrixConstraint_ImpliesThisIsAlsoSatisfied2 +Description: + + Verifies that the ImpliesThisIsAlsoSatisfied method correctly panics + when the input matrix constraint is malformed. +*/ +func TestMatrixConstraint_ImpliesThisIsAlsoSatisfied2(t *testing.T) { + // Constants + vm1 := symbolic.NewVariableMatrix(3, 2) + km2 := symbolic.DenseToKMatrix(symbolic.OnesMatrix(3, 3)) + + // Create matrix constraint + mConstr := symbolic.MatrixConstraint{vm1, km2, symbolic.SenseLessThanEqual} + + // Create normal matrix constraint to use as input + left := symbolic.DenseToKMatrix(symbolic.Identity(3)) + right := symbolic.DenseToKMatrix(symbolic.ZerosMatrix(3, 3)) + normalMC := symbolic.MatrixConstraint{left, right, symbolic.SenseLessThanEqual} + + expectedError := mConstr.Check() + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "Expected mConstr.ImpliesThisIsAlsoSatisfied(normalMC) to panic; did not panic", + ) + } + + // Check that the error is the expected error + err, ok := r.(error) + if !ok { + t.Errorf( + "Expected mConstr.ImpliesThisIsAlsoSatisfied(normalMC) to panic with type error; received %T", + r, + ) + } + if err.Error() != expectedError.Error() { + t.Errorf( + "Expected mConstr.ImpliesThisIsAlsoSatisfied(normalMC) to panic with error \"%v\"; received \"%v\"", + expectedError, + err, + ) + } + + }() + + // Call ImpliesThisIsAlsoSatisfied + mConstr.ImpliesThisIsAlsoSatisfied(normalMC) + + t.Errorf( + "Expected mConstr.ImpliesThisIsAlsoSatisfied(normalMC) to panic; did not panic", + ) +} + +/* +TestConstraint_ImpliesThisIsAlsoSatisfied3 +Description: + + Verifies that the ImpliesThisIsAlsoSatisfied method correctly panics + when the receiver is well-defined but the input constraint is not well-formed. +*/ +func TestConstraint_ImpliesThisIsAlsoSatisfied3(t *testing.T) { + // Constants + vm1 := symbolic.NewVariableMatrix(3, 3) + km2 := symbolic.DenseToKMatrix(symbolic.OnesMatrix(3, 3)) + + // Create matrix constraint + mConstr := symbolic.MatrixConstraint{vm1, km2, symbolic.SenseLessThanEqual} + + // Create malformed scalar constraint to use as input + scLeft := symbolic.Monomial{ + Exponents: []int{1}, + } + scRight := symbolic.K(5) + sc := symbolic.ScalarConstraint{scLeft, scRight, symbolic.SenseEqual} + expectedError := sc.Check() + + // Test + defer func() { + r := recover() + if r == nil { + t.Errorf( + "Expected mConstr.ImpliesThisIsAlsoSatisfied(sc) to panic; did not panic", + ) + } + + // Check that the error is the expected error + err, ok := r.(error) + if !ok { + t.Errorf( + "Expected mConstr.ImpliesThisIsAlsoSatisfied(sc) to panic with type error; received %T", + r, + ) + } + if err.Error() != expectedError.Error() { + t.Errorf( + "Expected mConstr.ImpliesThisIsAlsoSatisfied(sc) to panic with error \"%v\"; received \"%v\"", + expectedError, + err, + ) + } + + }() + + // Call ImpliesThisIsAlsoSatisfied + mConstr.ImpliesThisIsAlsoSatisfied(sc) + + t.Errorf( + "Expected mConstr.ImpliesThisIsAlsoSatisfied(sc) to panic; did not panic", + ) +} diff --git a/testing/symbolic/scalar_constraint_test.go b/testing/symbolic/scalar_constraint_test.go index 7343aca..9b5238d 100644 --- a/testing/symbolic/scalar_constraint_test.go +++ b/testing/symbolic/scalar_constraint_test.go @@ -1257,3 +1257,746 @@ func TestScalarConstraint_String1(t *testing.T) { ) } } + +/* +TestScalarConstraint_ScaleBy1 +Description: + + This tests that the ScalarConstraint.ScaleBy() method properly panics + when the left hand side is not a valid monomial. +*/ +func TestScalarConstraint_ScaleBy1(t *testing.T) { + // Constants + m1 := symbolic.Monomial{ + Coefficient: 1, + Exponents: []int{1}, + VariableFactors: []symbolic.Variable{}, + } + v2 := symbolic.NewVariable() + + // Create constraint + sc := symbolic.ScalarConstraint{ + LeftHandSide: m1, + RightHandSide: v2, + Sense: symbolic.SenseLessThanEqual, + } + + // Create the panic handling function + defer func() { + r := recover() + if r == nil { + t.Errorf( + "Expected sc.ScaleBy() to panic; received nil", + ) + } + + rAsError := r.(error) + expectedError := m1.Check() + if rAsError.Error() != expectedError.Error() { + t.Errorf( + "Expected sc.ScaleBy() to panic with error \"%v\"; received \"%v\"", + expectedError.Error(), + rAsError.Error(), + ) + } + }() + + sc.ScaleBy(2) + + t.Errorf( + "Expected sc.ScaleBy() to panic; received nil", + ) +} + +/* +TestScalarConstraint_ScaleBy2 +Description: + + This tests that the ScalarConstraint.ScaleBy() method properly + returns a new ScalarConstraint when the left hand side is a valid monomial. + In this case, we scale by a negative number, which should lead to a new + constraint with the opposite sense. +*/ +func TestScalarConstraint_ScaleBy2(t *testing.T) { + // Constants + x := symbolic.NewVariable() + y := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 1, + Exponents: []int{1, 1}, + VariableFactors: []symbolic.Variable{x, y}, + } + c2 := symbolic.K(3.14) + + // Create constraint + sc := symbolic.ScalarConstraint{ + LeftHandSide: m1, + RightHandSide: c2, + Sense: symbolic.SenseLessThanEqual, + } + + // Scale + newSc := sc.ScaleBy(-2) + + // Verify that the new left hand side is a monomial + if _, ok := newSc.Left().(symbolic.Monomial); !ok { + t.Errorf( + "Expected newSc.LeftHandSide to be a symbolic.Monomial; received %T", + newSc.Left(), + ) + } + + // Verify that the new right hand side is a constant 6.28 + m2, ok := newSc.Right().(symbolic.K) + if !ok { + t.Errorf( + "Expected newSc.RightHandSide to be a symbolic.K; received %T", + newSc.Right(), + ) + } + + // Verify that the new right hand side is a constant 6.28 + if float64(m2) != -6.28 { + t.Errorf( + "Expected newSc.RightHandSide to be 6.28; received %v", + m2, + ) + } + + // Verify that the new constraint has the opposite sense + if newSc.ConstrSense() != symbolic.SenseGreaterThanEqual { + t.Errorf( + "Expected newSc.Sense to be different from %v; received %v", + sc.Sense, + newSc.ConstrSense(), + ) + } +} + +/* +TestScalarConstraint_ScaleBy3 +Description: + + This tests that the ScalarConstraint.ScaleBy() method properly + returns a new ScalarConstraint when the left hand side is a valid monomial. + In this case, we scale by a positive number, which should lead to a new + constraint with the same sense. +*/ +func TestScalarConstraint_ScaleBy3(t *testing.T) { + // Constants + x := symbolic.NewVariable() + y := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 1, + Exponents: []int{1, 1}, + VariableFactors: []symbolic.Variable{x, y}, + } + c2 := symbolic.K(3.14) + + // Create constraint + sc := symbolic.ScalarConstraint{ + LeftHandSide: m1, + RightHandSide: c2, + Sense: symbolic.SenseLessThanEqual, + } + + // Scale + newSc := sc.ScaleBy(2) + + // Verify that the new left hand side is a monomial + if _, ok := newSc.Left().(symbolic.Monomial); !ok { + t.Errorf( + "Expected newSc.LeftHandSide to be a symbolic.Monomial; received %T", + newSc.Left(), + ) + } + + // Verify that the new right hand side is a constant 6.28 + m2, ok := newSc.Right().(symbolic.K) + if !ok { + t.Errorf( + "Expected newSc.RightHandSide to be a symbolic.K; received %T", + newSc.Right(), + ) + } + + // Verify that the new right hand side is a constant 6.28 + if float64(m2) != 6.28 { + t.Errorf( + "Expected newSc.RightHandSide to be 6.28; received %v", + m2, + ) + } + + // Verify that the new constraint has the same sense + if newSc.ConstrSense() != sc.Sense { + t.Errorf( + "Expected newSc.Sense to be %v; received %v", + sc.Sense, + newSc.ConstrSense(), + ) + } +} + +/* +TestScalarConstraint_ScaleBy4 +Description: + + This tests that the ScalarConstraint.ScaleBy() method properly + flips the sign of a constraint with the SenseGreaterThanEqual sense + when the scale factor is negative. +*/ +func TestScalarConstraint_ScaleBy4(t *testing.T) { + // Constants + x := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 1, + Exponents: []int{1}, + VariableFactors: []symbolic.Variable{x}, + } + c2 := symbolic.K(3.14) + + // Create constraint + sc := symbolic.ScalarConstraint{ + LeftHandSide: m1, + RightHandSide: c2, + Sense: symbolic.SenseGreaterThanEqual, + } + + // Scale + newSc := sc.ScaleBy(-2) + + // Verify that the new constraint has the opposite sense + if newSc.ConstrSense() != symbolic.SenseLessThanEqual { + t.Errorf( + "Expected newSc.Sense to be different from %v; received %v", + sc.Sense, + newSc.ConstrSense(), + ) + } +} + +/* +TestScalarConstraint_Variables1 +Description: + + Tests the Variables() method of a scalar constraint. This test verifies + that the method properly returns a slice of all variables in the constraint. + We will create a constraint with 2 variables (one in a monomial on the left + and a constant on the right). +*/ +func TestScalarConstraint_Variables1(t *testing.T) { + // Constants + x := symbolic.NewVariable() + y := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 1, + Exponents: []int{1, 1}, + VariableFactors: []symbolic.Variable{x, y}, + } + c2 := symbolic.K(3.14) + + // Create constraint + sc := symbolic.ScalarConstraint{ + LeftHandSide: m1, + RightHandSide: c2, + Sense: symbolic.SenseLessThanEqual, + } + + // Verify variables + vars := sc.Variables() + if len(vars) != 2 { + t.Errorf( + "Expected 2 variables; received %d", + len(vars), + ) + } +} + +/* +TestScalarConstraint_Variables2 +Description: + + Tests the Variables() method of a scalar constraint. This test verifies + that the method properly panics when the left hand side is not a valid monomial. +*/ +func TestScalarConstraint_Variables2(t *testing.T) { + // Constants + m1 := symbolic.Monomial{ + Coefficient: 1, + Exponents: []int{1}, + VariableFactors: []symbolic.Variable{}, + } + v2 := symbolic.NewVariable() + + // Create constraint + sc := symbolic.ScalarConstraint{ + LeftHandSide: m1, + RightHandSide: v2, + Sense: symbolic.SenseLessThanEqual, + } + + // Create the panic handling function + defer func() { + r := recover() + if r == nil { + t.Errorf( + "Expected sc.Variables() to panic; received nil", + ) + } + + rAsError := r.(error) + expectedError := m1.Check() + if rAsError.Error() != expectedError.Error() { + t.Errorf( + "Expected sc.Variables() to panic with error \"%v\"; received \"%v\"", + expectedError.Error(), + rAsError.Error(), + ) + } + }() + + sc.Variables() + + t.Errorf( + "Expected sc.Variables() to panic; received nil", + ) +} + +/* +TestScalarConstraint_ImpliesThisIsAlsoSatisfied1 +Description: + + Tests the ImpliesThisIsAlsoSatisfied() method of a scalar constraint. + This test verifies that the method properly panics when the given + scalar constraint is not valid (in this case, the left hand side is not + a valid monomial). +*/ +func TestScalarConstraint_ImpliesThisIsAlsoSatisfied1(t *testing.T) { + // Constants + m1 := symbolic.Monomial{ + Coefficient: 1, + Exponents: []int{1}, + VariableFactors: []symbolic.Variable{}, + } + v2 := symbolic.NewVariable() + + // Create constraint + sc := symbolic.ScalarConstraint{ + LeftHandSide: m1, + RightHandSide: v2, + Sense: symbolic.SenseLessThanEqual, + } + + // Create a second constraint + sc2 := v2.GreaterEq(symbolic.K(1)) + + // Create the panic handling function + defer func() { + r := recover() + if r == nil { + t.Errorf( + "Expected sc.ImpliesThisIsAlsoSatisfied() to panic; received nil", + ) + } + + rAsError := r.(error) + expectedError := m1.Check() + if rAsError.Error() != expectedError.Error() { + t.Errorf( + "Expected sc.ImpliesThisIsAlsoSatisfied() to panic with error \"%v\"; received \"%v\"", + expectedError.Error(), + rAsError.Error(), + ) + } + }() + + sc.ImpliesThisIsAlsoSatisfied(sc2) + + t.Errorf( + "Expected sc.ImpliesThisIsAlsoSatisfied() to panic; received nil", + ) +} + +/* +TestScalarConstraint_ImpliesThisIsAlsoSatisfied2 +Description: + + Tests the ImpliesThisIsAlsoSatisfied() method of a scalar constraint. + This test verifies that the method properly panics when the constraint + given as an argument is not valid (in this case, the left hand side is not + a valid monomial). +*/ +func TestScalarConstraint_ImpliesThisIsAlsoSatisfied2(t *testing.T) { + // Constants + x := symbolic.NewVariable() + y := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 1, + Exponents: []int{1, 1}, + VariableFactors: []symbolic.Variable{x, y}, + } + c2 := symbolic.K(3.14) + + // Create constraint + sc := symbolic.ScalarConstraint{ + LeftHandSide: m1, + RightHandSide: c2, + Sense: symbolic.SenseLessThanEqual, + } + + // Create a second constraint + m2 := symbolic.Monomial{ + Coefficient: 1, + Exponents: []int{1}, + VariableFactors: []symbolic.Variable{}, + } + v2 := symbolic.NewVariable() + sc2 := symbolic.ScalarConstraint{ + LeftHandSide: m2, + RightHandSide: v2, + Sense: symbolic.SenseGreaterThanEqual, + } + + // Create the panic handling function + defer func() { + r := recover() + if r == nil { + t.Errorf( + "Expected sc.ImpliesThisIsAlsoSatisfied() to panic; received nil", + ) + } + + rAsError := r.(error) + expectedError := m2.Check() + if rAsError.Error() != expectedError.Error() { + t.Errorf( + "Expected sc.ImpliesThisIsAlsoSatisfied() to panic with error \"%v\"; received \"%v\"", + expectedError.Error(), + rAsError.Error(), + ) + } + }() + + sc.ImpliesThisIsAlsoSatisfied(sc2) + + t.Errorf( + "Expected sc.ImpliesThisIsAlsoSatisfied() to panic; received nil", + ) +} + +/* +TestScalarConstraint_ImpliesThisIsAlsoSatisfied3 +Description: + + Tests the ImpliesThisIsAlsoSatisfied() method of a scalar constraint. + This test verifies that the method properly returns false when the + constraint does not imply the other constraint. In this case, we have + x + y <= 3 and y >= 1. The first constraint does NOT imply the second. +*/ +func TestScalarConstraint_ImpliesThisIsAlsoSatisfied3(t *testing.T) { + // Constants + x := symbolic.NewVariable() + y := symbolic.NewVariable() + c2 := symbolic.K(3.0) + + // Create constraint + sc := x.Plus(y).LessEq(c2) + + // Create a second constraint + sc2 := y.GreaterEq(symbolic.K(1)) + + // Verify that the first constraint does NOT imply the second + if sc.ImpliesThisIsAlsoSatisfied(sc2) { + t.Errorf( + "Expected sc.ImpliesThisIsAlsoSatisfied(sc2) to be false; received true", + ) + } +} + +/* +TestScalarConstraint_ImpliesThisIsAlsoSatisfied4 +Description: + + Tests the ImpliesThisIsAlsoSatisfied() method of a scalar constraint. + This test verifies that the method properly returns true when the + constraint DOES imply the other constraint AND they are both single-variable + constraints. In this case, we have + x <= 3 and x <= 4. The first constraint DOES imply the second. +*/ +func TestScalarConstraint_ImpliesThisIsAlsoSatisfied4(t *testing.T) { + // Constants + x := symbolic.NewVariable() + + // Create constraint + sc := x.LessEq(3.0) + + // Create a second constraint + sc2 := x.LessEq(4.0) + + // Verify that the first constraint DOES imply the second + if !sc.ImpliesThisIsAlsoSatisfied(sc2) { + t.Errorf( + "Expected sc.ImpliesThisIsAlsoSatisfied(sc2) to be true; received false", + ) + } +} + +/* +TestScalarConstraint_ImpliesThisIsAlsoSatisfied5 +Description: + + Tests the ImpliesThisIsAlsoSatisfied() method of a scalar constraint. + This test verifies that the method properly returns true when the + constraint DOES imply the other constraint AND they are both single-variable + constraints. In this case, we have + x >= 4 and x >= 3. The first constraint DOES imply the second. +*/ +func TestScalarConstraint_ImpliesThisIsAlsoSatisfied5(t *testing.T) { + // Constants + x := symbolic.NewVariable() + + // Create constraint + sc := x.GreaterEq(4.0) + + // Create a second constraint + sc2 := x.GreaterEq(3.0) + + // Verify that the first constraint DOES imply the second + if !sc.ImpliesThisIsAlsoSatisfied(sc2) { + t.Errorf( + "Expected sc.ImpliesThisIsAlsoSatisfied(sc2) to be true; received false", + ) + } +} + +/* +TestScalarConstraint_ImpliesThisIsAlsoSatisfied6 +Description: + + Tests the ImpliesThisIsAlsoSatisfied() method of a scalar constraint. + This test verifies that the method properly returns true when the + constraint DOES imply the other constraint AND they are both single-variable + constraints. In this case, we have + x = 3 and x <= 4. The first constraint DOES imply the second. +*/ +func TestScalarConstraint_ImpliesThisIsAlsoSatisfied6(t *testing.T) { + // Constants + x := symbolic.NewVariable() + + // Create constraint + sc := x.Eq(3.0) + + // Create a second constraint + sc2 := x.LessEq(4.0) + + // Verify that the first constraint DOES imply the second + if !sc.ImpliesThisIsAlsoSatisfied(sc2) { + t.Errorf( + "Expected sc.ImpliesThisIsAlsoSatisfied(sc2) to be true; received false", + ) + } +} + +/* +TestScalarConstraint_ImpliesThisIsAlsoSatisfied7 +Description: + + Tests the ImpliesThisIsAlsoSatisfied() method of a scalar constraint. + This test verifies that the method properly returns true when the + constraint DOES imply the other constraint AND they are both single-variable + constraints. In this case, we have + x = 3 and x >= 2. The first constraint DOES imply the second. +*/ +func TestScalarConstraint_ImpliesThisIsAlsoSatisfied7(t *testing.T) { + // Constants + x := symbolic.NewVariable() + + // Create constraint + sc := x.Eq(3.0) + + // Create a second constraint + sc2 := x.GreaterEq(2.0) + + // Verify that the first constraint DOES imply the second + if !sc.ImpliesThisIsAlsoSatisfied(sc2) { + t.Errorf( + "Expected sc.ImpliesThisIsAlsoSatisfied(sc2) to be true; received false", + ) + } +} + +/* +TestScalarConstraint_ImpliesThisIsAlsoSatisfied8 +Description: + + Tests the ImpliesThisIsAlsoSatisfied() method of a scalar constraint. + This test verifies that the method properly handles the case when both + constraints contain negative coefficients on the same variable. + In this case, we have: + -2 x <= 4 and + -2 x <= 5 + as the input constraints. The first constraint DOES imply the second. +*/ +func TestScalarConstraint_ImpliesThisIsAlsoSatisfied8(t *testing.T) { + // Constants + x := symbolic.NewVariable() + + // Create constraint + sc := x.Multiply(-2).LessEq(4.0) + + // Create a second constraint + sc2 := x.Multiply(-2).LessEq(5.0) + + // Verify that the first constraint DOES imply the second + if !sc.ImpliesThisIsAlsoSatisfied(sc2) { + t.Errorf( + "Expected sc.ImpliesThisIsAlsoSatisfied(sc2) to be true; received false", + ) + } +} + +/* +TestScalarConstraint_ImpliesThisIsAlsoSatisfied9 +Description: + + Tests the ImpliesThisIsAlsoSatisfied() method of a scalar constraint. + This test verifies that the method properly handles the case when both + constraints are SenseGreaterThanEqual and have positive coefficients + on the same variable, but they do NOT imply each other. + In this case, we have: + 2 x >= 4 and + 2 x >= 5 + as the input constraints. The first constraint does NOT imply the second. +*/ +func TestScalarConstraint_ImpliesThisIsAlsoSatisfied9(t *testing.T) { + // Constants + x := symbolic.NewVariable() + + // Create constraint + sc := x.Multiply(2).GreaterEq(4.0) + + // Create a second constraint + sc2 := x.Multiply(2).GreaterEq(5.0) + + // Verify that the first constraint does NOT imply the second + if sc.ImpliesThisIsAlsoSatisfied(sc2) { + t.Errorf( + "Expected sc.ImpliesThisIsAlsoSatisfied(sc2) to be false; received true", + ) + } +} + +/* +TestScalarConstraint_ImpliesThisIsAlsoSatisfied10 +Description: + + Tests the ImpliesThisIsAlsoSatisfied() method of a scalar constraint. + This test verifies that the method properly handles the case when both + cosntraints are SenseEqual and have positive coefficients + on the same variable, but they do NOT imply each other. + In this case, we have: + 2 x = 4 and + 2 x = 5 + as the input constraints. The first constraint does NOT imply the second. +*/ +func TestScalarConstraint_ImpliesThisIsAlsoSatisfied10(t *testing.T) { + // Constants + x := symbolic.NewVariable() + + // Create constraint + sc := x.Multiply(2).Eq(4.0) + + // Create a second constraint + sc2 := x.Multiply(2).Eq(5.0) + + // Verify that the first constraint does NOT imply the second + if sc.ImpliesThisIsAlsoSatisfied(sc2) { + t.Errorf( + "Expected sc.ImpliesThisIsAlsoSatisfied(sc2) to be false; received true", + ) + } +} + +/* +TestScalarConstraint_AsSimplifiedConstraint1 +Description: + + This function tests the AsSimplifiedConstraint() method of a scalar constraint. + We will create a scalar constraint with a monomial on the left hand side + and a polynomial on the right hand side. The expected output is a new + constraint with a polynomial on the left hand side and a constant on the + right hand side. +*/ +func TestScalarConstraint_AsSimplifiedConstraint1(t *testing.T) { + // Constants + x := symbolic.NewVariable() + y := symbolic.NewVariable() + m1 := symbolic.Monomial{ + Coefficient: 2, + Exponents: []int{1, 1}, + VariableFactors: []symbolic.Variable{x, y}, + } + p2 := symbolic.Polynomial{ + Monomials: []symbolic.Monomial{ + { + Coefficient: 1, + Exponents: []int{1}, + VariableFactors: []symbolic.Variable{x}, + }, + { + Coefficient: 3, + Exponents: []int{1}, + VariableFactors: []symbolic.Variable{y}, + }, + { + Coefficient: 4, + Exponents: []int{}, + VariableFactors: []symbolic.Variable{}, + }, + }, + } + + // Create constraint + sc := symbolic.ScalarConstraint{ + LeftHandSide: m1, + RightHandSide: p2, + Sense: symbolic.SenseLessThanEqual, + } + + // Get simplified constraint + simplifiedSc := sc.AsSimplifiedConstraint() + + // Verify that the left hand side is a polynomial + if _, ok := simplifiedSc.Left().(symbolic.Polynomial); !ok { + t.Errorf( + "Expected simplifiedSc.LeftHandSide to be a symbolic.Polynomial; received %T", + simplifiedSc.Left(), + ) + } + + // Verify that the right hand side is a constant + k, ok := simplifiedSc.Right().(symbolic.K) + if !ok { + t.Errorf( + "Expected simplifiedSc.RightHandSide to be a symbolic.K; received %T", + simplifiedSc.Right(), + ) + } + + if float64(k) != 4 { + t.Errorf( + "Expected simplifiedSc.RightHandSide to be 4; received %v", + k, + ) + } + + // Verify that the sense is the same + if simplifiedSc.ConstrSense() != sc.ConstrSense() { + t.Errorf( + "Expected simplifiedSc.Sense to be %v; received %v", + sc.ConstrSense(), + simplifiedSc.ConstrSense(), + ) + } +} diff --git a/testing/symbolic/vector_constraint_test.go b/testing/symbolic/vector_constraint_test.go index 2fee4d9..d86e9d5 100644 --- a/testing/symbolic/vector_constraint_test.go +++ b/testing/symbolic/vector_constraint_test.go @@ -1040,3 +1040,359 @@ func TestVectorConstraint_SubstituteAccordingTo2(t *testing.T) { ) } } + +/* +TestVectorConstraint_AsSimplifiedConstraint1 +Description: + + This function tests that the AsSimplifiedConstraint method properly + panics if the input vector constraint is not well-defined. + (In this case, the left and right hand sides have different lengths.) +*/ +func TestVectorConstraint_AsSimplifiedConstraint1(t *testing.T) { + // Setup + N := 7 + left := symbolic.VecDenseToKVector(symbolic.OnesVector(N)) + right := symbolic.NewVariableVector(N + 1) + + // Create the vector constraint + vc := symbolic.VectorConstraint{left, right, symbolic.SenseLessThanEqual} + + // Create the function for handling the panic + defer func() { + r := recover() + if r == nil { + t.Errorf( + "Expected vc.AsSimplifiedConstraint() to panic; received nil", + ) + } + + rAsError := r.(error) + expectedError := smErrors.VectorDimensionError{ + Operation: fmt.Sprintf("Comparison (%v)", vc.Sense), + Arg1: vc.LeftHandSide, + Arg2: vc.RightHandSide, + } + if rAsError.Error() != expectedError.Error() { + t.Errorf( + "Expected vc.AsSimplifiedConstraint() to panic with error \"%v\"; received \"%v\"", + expectedError.Error(), + rAsError.Error(), + ) + } + }() + + // Test + vc.AsSimplifiedConstraint() + + // Raise an error if the test did not panic + t.Errorf( + "Expected vc.AsSimplifiedConstraint() to panic; received nil", + ) + +} + +/* +TestVectorConstraint_AsSimplifiedConstraint2 +Description: + + This function tests that the AsSimplifiedConstraint method properly + returns a simplified vector constraint when the original vector constraint + is well-defined. In this case, the original vector constraint will have + a left hand side that is a vector of monomials and a right hand side + that is a vector of polynomials. + After simplification, the left hand side should be a vector of polynomials + and the right hand side should be a vector of constants. +*/ +func TestVectorConstraint_AsSimplifiedConstraint2(t *testing.T) { + // Setup + N := 7 + x := symbolic.NewVariableVector(N) + left := x.ToMonomialVector() + right := x.Multiply(2.0).Plus(symbolic.VecDenseToKVector(symbolic.OnesVector(N))).(symbolic.PolynomialVector) + + // Create the vector constraint + vc := symbolic.VectorConstraint{left, right, symbolic.SenseLessThanEqual} + + // Test + simplifiedVC := vc.AsSimplifiedConstraint().(symbolic.VectorConstraint) + + // Check the left hand side + // it should now be a vector of polynomials + if _, ok := simplifiedVC.LeftHandSide.(symbolic.PolynomialVector); !ok { + t.Errorf( + "Expected vc.AsSimplifiedConstraint() to return a vector constraint with a polynomial vector on the left hand side; received %v", + simplifiedVC.LeftHandSide, + ) + } + + // Check the right hand side + if _, ok := simplifiedVC.RightHandSide.(symbolic.KVector); !ok { + t.Errorf( + "Expected vc.AsSimplifiedConstraint() to return a vector constraint with a constant vector on the right hand side; received %v", + simplifiedVC.RightHandSide, + ) + } + + // Check the sense + if simplifiedVC.Sense != vc.Sense { + t.Errorf( + "Expected vc.AsSimplifiedConstraint() to return a vector constraint with the same sense; received %v", + simplifiedVC.Sense, + ) + } +} + +/* +TestVectorConstraint_Variables1 +Description: + + This function tests that the Variables method properly + returns the correct set of variables in a well-defined vector constraint. + In this case, the left hand side will be a vector of N variables + and the right hand side will be a vector of N different variables. + The result should be a set containing all 2N variables. +*/ +func TestVectorConstraint_Variables1(t *testing.T) { + // Setup + N := 7 + x := symbolic.NewVariableVector(N) + y := symbolic.NewVariableVector(N) + left := x + right := y + + // Create the vector constraint + vc := symbolic.VectorConstraint{left, right, symbolic.SenseLessThanEqual} + + // Test + vars := vc.Variables() + + // Check the length of the variable set + if len(vars) != 2*N { + t.Errorf( + "Expected vc.Variables() to return a set with %v variables; received %v", + 2*N, + len(vars), + ) + } + + // Check that all variables are present in the set + for i := 0; i < N; i++ { + idx, err := symbolic.FindInSlice(x[i], vars) + if err != nil { + t.Errorf( + "Received an error when searching for variable %v in the set: %v", + x[i], + err) + } + if idx == -1 { + t.Errorf( + "Expected vc.Variables() to return a set containing variable %v; it was not found in the set", + x[i], + ) + } + // Check for y[i] + idx, err = symbolic.FindInSlice(y[i], vars) + if err != nil { + t.Errorf( + "Received an error when searching for variable %v in the set: %v", + y[i], + err) + } + + if idx == -1 { + t.Errorf( + "Expected vc.Variables() to return a set containing variable %v; it was not found in the set", + y[i], + ) + } + } +} + +/* +TestVectorConstraint_ImpliesThisIsAlsoSatisfied1 +Description: + + This function tests that the ImpliesThisIsAlsoSatisfied method properly + panics if the receiver vector constraint is not well-defined. + (In this case, the left and right hand sides have different lengths.) +*/ +func TestVectorConstraint_ImpliesThisIsAlsoSatisfied1(t *testing.T) { + // Setup + N := 7 + x := symbolic.NewVariableVector(N) + y := symbolic.NewVariableVector(N) + left := x + right := symbolic.NewVariableVector(N - 1) + + // Create the vector constraint + vc := symbolic.VectorConstraint{left, right, symbolic.SenseLessThanEqual} + + // Create the second vector constraint + vc2 := symbolic.VectorConstraint{x, y, symbolic.SenseLessThanEqual} + + // Test + expectedError := vc.Check() + defer func() { + r := recover() + if r == nil { + t.Errorf( + "Expected vc.ImpliesThisIsAlsoSatisfied() to panic; received nil", + ) + } + + rAsError := r.(error) + if rAsError.Error() != expectedError.Error() { + t.Errorf( + "Expected vc.ImpliesThisIsAlsoSatisfied() to panic with error \"%v\"; received \"%v\"", + expectedError.Error(), + rAsError.Error(), + ) + } + }() + + vc.ImpliesThisIsAlsoSatisfied(vc2) +} + +/* +TestVectorConstraint_ImpliesThisIsAlsoSatisfied2 +Description: + + This function tests that the ImpliesThisIsAlsoSatisfied method properly + panics if: + - The receiver vector constraint is well-defined, but + - The input vector constraint is not well-defined. + (In this case, the left and right hand sides of the input vector constraint + have different lengths.) +*/ +func TestVectorConstraint_ImpliesThisIsAlsoSatisfied2(t *testing.T) { + // Setup + N := 7 + x := symbolic.NewVariableVector(N) + y := symbolic.NewVariableVector(N) + left := x + right := y + + // Create the vector constraint + vc := symbolic.VectorConstraint{left, right, symbolic.SenseLessThanEqual} + + // Create the second vector constraint + vc2 := symbolic.VectorConstraint{x, symbolic.NewVariableVector(N - 1), symbolic.SenseLessThanEqual} + + // Test + expectedError := vc2.Check() + defer func() { + r := recover() + if r == nil { + t.Errorf( + "Expected vc.ImpliesThisIsAlsoSatisfied() to panic; received nil", + ) + } + + rAsError := r.(error) + if rAsError.Error() != expectedError.Error() { + t.Errorf( + "Expected vc.ImpliesThisIsAlsoSatisfied() to panic with error \"%v\"; received \"%v\"", + expectedError.Error(), + rAsError.Error(), + ) + } + }() + + vc.ImpliesThisIsAlsoSatisfied(vc2) +} + +/* +TestVectorConstraint_ImpliesThisIsAlsoSatisfied3 +Description: + + This function tests that the ImpliesThisIsAlsoSatisfied method properly + returns false if the receiver and input vector constraints are well-defined, + but the input constraint is not implied by the receiver constraint. + In this case, the receiver constraint will be: + [1, 0; + 0, 1] * x <= [1; 1] + and the input constraint will be: + [1, 0; + 0, 1] * x <= [2; 2] + + TODO: This constraint should return true, but the implementation + does not currently handle this case. Let's fix this in a future update. +*/ +func TestVectorConstraint_ImpliesThisIsAlsoSatisfied3(t *testing.T) { + // Setup + N := 2 + x := symbolic.NewVariableVector(N) + left := x + right := mat.NewVecDense(N, []float64{1, 1}) + + // Create the vector constraint + vc := symbolic.VectorConstraint{ + LeftHandSide: left, + RightHandSide: symbolic.VecDenseToKVector(*right), + Sense: symbolic.SenseLessThanEqual, + } + + // Create the second vector constraint + right2 := mat.NewVecDense(N, []float64{2, 2}) + vc2 := symbolic.VectorConstraint{ + LeftHandSide: left, + RightHandSide: symbolic.VecDenseToKVector(*right2), + Sense: symbolic.SenseLessThanEqual, + } + + // Test + result := vc.ImpliesThisIsAlsoSatisfied(vc2) // TODO: Fix this test to return true + if result { + t.Errorf( + "Expected vc.ImpliesThisIsAlsoSatisfied() to return true; received false", + ) + } +} + +/* +TestVectorConstraint_ImpliesThisIsAlsoSatisfied4 +Description: + + This function tests that the ImpliesThisIsAlsoSatisfied method properly + returns true if: + - the receiver vector constraints are well-defined, + - the input is a well-defined scalar constraint, and + - the input constraint is implied by one of the receiver constraints. + In this case, the receiver constraint will be: + [1, 0; + 0, 1] * x <= [1; 1] + and the input constraint will be: + [1, 0] * x <= 1 +*/ +func TestVectorConstraint_ImpliesThisIsAlsoSatisfied4(t *testing.T) { + // Setup + N := 2 + x := symbolic.NewVariableVector(N) + left := x + right := mat.NewVecDense(N, []float64{1, 1}) + + // Create the vector constraint + vc := symbolic.VectorConstraint{ + LeftHandSide: left, + RightHandSide: symbolic.VecDenseToKVector(*right), + Sense: symbolic.SenseLessThanEqual, + } + + // Create the scalar constraint + left2 := x.AtVec(0) + right2 := symbolic.K(1) + sc := symbolic.ScalarConstraint{ + LeftHandSide: left2, + RightHandSide: right2, + Sense: symbolic.SenseLessThanEqual, + } + + // Test + result := vc.ImpliesThisIsAlsoSatisfied(sc) + if !result { + t.Errorf( + "Expected vc.ImpliesThisIsAlsoSatisfied() to return true; received false", + ) + } +}