diff --git a/.github/workflows/coverage1.yml b/.github/workflows/coverage1.yml index 9a47e37..439650f 100644 --- a/.github/workflows/coverage1.yml +++ b/.github/workflows/coverage1.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - go: [1.19] + go: [1.23] permissions: # Give the default GITHUB_TOKEN write permission to commit and push the # added or changed files to the repository. diff --git a/go.mod b/go.mod index be1122f..d26be7e 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,7 @@ module github.com/MatProGo-dev/SymbolicMath.go -go 1.21 +go 1.23.0 -require gonum.org/v1/gonum v0.14.0 +toolchain go1.23.8 + +require gonum.org/v1/gonum v0.16.0 diff --git a/go.sum b/go.sum index 0a3f045..83477d1 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,2 @@ -golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= -gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= diff --git a/symbolic/constraint.go b/symbolic/constraint.go index ee3b905..e375fd1 100644 --- a/symbolic/constraint.go +++ b/symbolic/constraint.go @@ -13,6 +13,8 @@ type Constraint interface { ConstrSense() ConstrSense Check() error IsLinear() bool + Substitute(vIn Variable, seIn ScalarExpression) Constraint + SubstituteAccordingTo(subMap map[Variable]Expression) Constraint } func IsConstraint(c interface{}) bool { diff --git a/symbolic/matrix_constraint.go b/symbolic/matrix_constraint.go index 6f0e432..0715ef9 100644 --- a/symbolic/matrix_constraint.go +++ b/symbolic/matrix_constraint.go @@ -2,6 +2,7 @@ package symbolic import ( "fmt" + "github.com/MatProGo-dev/SymbolicMath.go/smErrors" ) @@ -145,3 +146,48 @@ Description: func (mc MatrixConstraint) IsLinear() bool { return IsLinear(mc.RightHandSide) && IsLinear(mc.LeftHandSide) } + +/* +Substitute +Description: + + Substitutes the variable vIn with the scalar expression seIn +*/ +func (mc MatrixConstraint) Substitute(vIn Variable, seIn ScalarExpression) Constraint { + // Check that the constraint is well formed. + err := mc.Check() + if err != nil { + panic(err) + } + + // Substitute the variable in the left hand side + newLHS := mc.LeftHandSide.Substitute(vIn, seIn).(MatrixExpression) + + // Substitute the variable in the right hand side + newRHS := mc.RightHandSide.Substitute(vIn, seIn).(MatrixExpression) + + return MatrixConstraint{newLHS, newRHS, mc.Sense} +} + +/* +SubstituteAccordingTo +Description: + + Substitutes the variables in the map with the corresponding expressions + in the given scalar constraint. +*/ +func (mc MatrixConstraint) SubstituteAccordingTo(subMap map[Variable]Expression) Constraint { + // Check that the constraint is well formed. + err := mc.Check() + if err != nil { + panic(err) + } + + // Substitute the variable in the left hand side + newLHS := mc.LeftHandSide.SubstituteAccordingTo(subMap).(MatrixExpression) + + // Substitute the variable in the right hand side + newRHS := mc.RightHandSide.SubstituteAccordingTo(subMap).(MatrixExpression) + + return MatrixConstraint{newLHS, newRHS, mc.Sense} +} diff --git a/symbolic/monomial.go b/symbolic/monomial.go index 880b334..ac1440b 100644 --- a/symbolic/monomial.go +++ b/symbolic/monomial.go @@ -727,11 +727,13 @@ func (m Monomial) SubstituteAccordingTo(subMap map[Variable]Expression) Expressi // Algorithm // Create the monomial - var out Expression = K(0.0) + var out Expression = K(m.Coefficient) // Iterate through each variable in the monomial - for tempVar, tempExp := range subMap { - out = out.Substitute(tempVar, tempExp.(ScalarExpression)) + for ii, varII := range m.VariableFactors { + out = out.Multiply( + varII.SubstituteAccordingTo(subMap).Power(m.Exponents[ii]), + ) } // Return diff --git a/symbolic/scalar_constraint.go b/symbolic/scalar_constraint.go index 751a94e..18cffbb 100644 --- a/symbolic/scalar_constraint.go +++ b/symbolic/scalar_constraint.go @@ -208,3 +208,76 @@ func (sc ScalarConstraint) LinearEqualityConstraintRepresentation(wrt ...[]Varia // Return return C, d } + +/* +Substitute +Description: + + Substitutes the variable vIn with the scalar expression seIn in the + given scalar constraint. +*/ +func (sc ScalarConstraint) Substitute(vIn Variable, seIn ScalarExpression) Constraint { + // Check that the constraint is well formed. + err := sc.Check() + if err != nil { + panic(err) + } + + // Substitute the variable in the left hand side + newLHS := sc.LeftHandSide.Substitute(vIn, seIn).(ScalarExpression) + + // Substitute the variable in the right hand side + newRHS := sc.RightHandSide.Substitute(vIn, seIn).(ScalarExpression) + + // Return the new constraint + return ScalarConstraint{ + LeftHandSide: newLHS, + RightHandSide: newRHS, + Sense: sc.Sense, + } +} + +/* +SubstituteAccordingTo +Description: + + Substitutes the variables in the map with the corresponding expressions + in the given scalar constraint. +*/ +func (sc ScalarConstraint) SubstituteAccordingTo(subMap map[Variable]Expression) Constraint { + // Check that the constraint is well formed. + err := sc.Check() + if err != nil { + panic(err) + } + + // Substitute the variable in the left hand side + newLHS := sc.LeftHandSide.SubstituteAccordingTo(subMap).(ScalarExpression) + + // Substitute the variable in the right hand side + newRHS := sc.RightHandSide.SubstituteAccordingTo(subMap).(ScalarExpression) + + // Return the new constraint + return ScalarConstraint{ + LeftHandSide: newLHS, + RightHandSide: newRHS, + Sense: sc.Sense, + } +} + +/* +String +Description: + + Returns a string representation of the scalar constraint. +*/ +func (sc ScalarConstraint) String() string { + // Check that the constraint is well formed. + err := sc.Check() + if err != nil { + panic(err) + } + + // Create the string representation + return sc.LeftHandSide.String() + " " + sc.Sense.String() + " " + sc.RightHandSide.String() +} diff --git a/symbolic/vector_constraint.go b/symbolic/vector_constraint.go index 1167025..1bd8bea 100644 --- a/symbolic/vector_constraint.go +++ b/symbolic/vector_constraint.go @@ -257,3 +257,62 @@ func (vc VectorConstraint) LinearEqualityConstraintRepresentation(wrt ...[]Varia // Return the tuple return C, d } + +/* +Substitute +Description: + + Substitutes the variable vIn with the scalar expression seIn in the vector constraint. +*/ +func (vc VectorConstraint) Substitute(vIn Variable, seIn ScalarExpression) Constraint { + // Check that the constraint is well formed. + err := vc.Check() + if err != nil { + panic(err) + } + + // Substitute the variable in the left and right hand sides. + newLHS := vc.LeftHandSide.Substitute(vIn, seIn).(VectorExpression) + newRHS := vc.RightHandSide.Substitute(vIn, seIn).(VectorExpression) + + // Return the new constraint + return VectorConstraint{newLHS, newRHS, vc.Sense} +} + +/* +SubstituteAccordingTo +Description: + + Substitutes the variables in the map with the corresponding expressions +*/ +func (vc VectorConstraint) SubstituteAccordingTo(subMap map[Variable]Expression) Constraint { + // Check that the constraint is well formed. + err := vc.Check() + if err != nil { + panic(err) + } + + // Substitute the variable in the left and right hand sides. + newLHS := vc.LeftHandSide.SubstituteAccordingTo(subMap).(VectorExpression) + newRHS := vc.RightHandSide.SubstituteAccordingTo(subMap).(VectorExpression) + + // Return the new constraint + return VectorConstraint{newLHS, newRHS, vc.Sense} +} + +/* +Len +Description: + + Returns the length of the vector constraint. +*/ +func (vc VectorConstraint) Len() int { + // Check that the constraint is well formed. + err := vc.Check() + if err != nil { + panic(err) + } + + // Return the length of the vector constraint. + return vc.LeftHandSide.Len() +} diff --git a/testing/symbolic/matrix_constraint_test.go b/testing/symbolic/matrix_constraint_test.go index 1a1e8d1..b939c26 100644 --- a/testing/symbolic/matrix_constraint_test.go +++ b/testing/symbolic/matrix_constraint_test.go @@ -8,9 +8,10 @@ Description: import ( "fmt" + "testing" + "github.com/MatProGo-dev/SymbolicMath.go/smErrors" "github.com/MatProGo-dev/SymbolicMath.go/symbolic" - "testing" ) /* @@ -432,3 +433,211 @@ func TestMatrixConstraint_At3(t *testing.T) { mc.At(0, 0) } + +/* +TestMatrixConstraint_Substitute1 +Description: + + Tests that the Substitute() method properly panics + when the left hand side is not a well-formed matrix expression. + (In this case, the left hand side is a monomial matrix that is not well-formed) +*/ +func TestMatrixConstraint_Substitute1(t *testing.T) { + // Constants + 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.Substitute() to panic; did not panic", + ) + } + + // Check that the error is the expected error + err, ok := r.(error) + if !ok { + t.Errorf( + "Expected mc.Substitute() to panic with type error; received %T", + r, + ) + } + + if err.Error() != expectedError.Error() { + t.Errorf( + "Expected mc.Substitute() to panic with error \"%v\"; received \"%v\"", + expectedError, + err, + ) + } + + }() + + mc.Substitute(symbolic.NewVariable(), symbolic.NewVariable()) + + t.Errorf( + "Expected mc.Substitute() to panic; did not panic", + ) +} + +/* +TestMatrixConstraint_Substitute2 +Description: + + Tests that the Substitute() method properly returns a + well-formed matrix constraint when the left and right hand sides + are well-formed and the sense is well-formed. + The left hand side is a monomial matrix and the right hand side + is a constant matrix. + After substitution, the left hand side should be a polynomial matrix + and the right hand side should be a constant matrix. +*/ +func TestMatrixConstraint_Substitute2(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + v2 := symbolic.NewVariable() + left := symbolic.MonomialMatrix{ + {v1.ToMonomial(), v1.ToMonomial(), v1.ToMonomial(), v1.ToMonomial()}, + } + right := symbolic.DenseToKMatrix(symbolic.ZerosMatrix(1, 4)) + mc := symbolic.MatrixConstraint{left, right, symbolic.SenseLessThanEqual} + + // Test + mcSubstituted := mc.Substitute(v1, v2.Plus(v2).(symbolic.ScalarExpression)) + + // Verify that mcSubstituted is a MatrixConstraint type + mcSubstitutedAsMC, ok := mcSubstituted.(symbolic.MatrixConstraint) + if !ok { + t.Errorf( + "Expected mcSubstituted to be of type MatrixConstraint; received %T", + mcSubstituted, + ) + } + + // Verify that the left hand side is a polynomial matrix + if _, ok := mcSubstitutedAsMC.LeftHandSide.(symbolic.PolynomialMatrix); !ok { + t.Errorf( + "Expected mcSubstituted.LeftHandSide to be of type PolynomialMatrix; received %T", + mcSubstitutedAsMC.LeftHandSide, + ) + } + + // Verify that the right hand side is a constant matrix + if _, ok := mcSubstitutedAsMC.RightHandSide.(symbolic.KMatrix); !ok { + t.Errorf( + "Expected mcSubstituted.RightHandSide to be of type KMatrix; received %T", + mcSubstitutedAsMC.RightHandSide, + ) + } +} + +/* +TestMatrixConstraint_SubstituteAccordingTo1 +Description: + + Tests that the SubstituteAccordingTo() method properly panics + when the left hand side is not a well-formed matrix expression. + (In this case, the left hand side is a monomial matrix that is not well-formed) +*/ +func TestMatrixConstraint_SubstituteAccordingTo1(t *testing.T) { + // Constants + 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, + ) + } + + }() + + mc.SubstituteAccordingTo(map[symbolic.Variable]symbolic.Expression{}) + + t.Errorf( + "Expected mc.SubstituteAccordingTo() to panic; did not panic", + ) +} + +/* +TestMatrixConstraint_SubstituteAccordingTo2 +Description: + + Tests that the SubstituteAccordingTo() method properly returns a + well-formed matrix constraint when the left and right hand sides + are well-formed and the sense is well-formed. + The left hand side is a monomial matrix and the right hand side + is a constant matrix. + After substitution, the left hand side should be a polynomial matrix + and the right hand side should be a constant matrix. +*/ +func TestMatrixConstraint_SubstituteAccordingTo2(t *testing.T) { + // Constants + v1 := symbolic.NewVariable() + v2 := symbolic.NewVariable() + left := symbolic.MonomialMatrix{ + {v1.ToMonomial(), v1.ToMonomial(), v1.ToMonomial(), v1.ToMonomial()}, + } + right := symbolic.DenseToKMatrix(symbolic.ZerosMatrix(1, 4)) + mc := symbolic.MatrixConstraint{left, right, symbolic.SenseLessThanEqual} + + // Test + mcSubstituted := mc.SubstituteAccordingTo( + map[symbolic.Variable]symbolic.Expression{ + v1: v2.Plus(v2).(symbolic.ScalarExpression), + }, + ) + + // Verify that mcSubstituted is a MatrixConstraint type + mcSubstitutedAsMC, ok := mcSubstituted.(symbolic.MatrixConstraint) + if !ok { + t.Errorf( + "Expected mcSubstituted to be of type MatrixConstraint; received %T", + mcSubstituted, + ) + } + + // Verify that the left hand side is a polynomial matrix + if _, ok := mcSubstitutedAsMC.LeftHandSide.(symbolic.PolynomialMatrix); !ok { + t.Errorf( + "Expected mcSubstituted.LeftHandSide to be of type PolynomialMatrix; received %T", + mcSubstitutedAsMC.LeftHandSide, + ) + } + + // Verify that the right hand side is a constant matrix + if _, ok := mcSubstitutedAsMC.RightHandSide.(symbolic.KMatrix); !ok { + t.Errorf( + "Expected mcSubstituted.RightHandSide to be of type KMatrix; received %T", + mcSubstitutedAsMC.RightHandSide, + ) + } +} diff --git a/testing/symbolic/scalar_constraint_test.go b/testing/symbolic/scalar_constraint_test.go index 9c1f217..7343aca 100644 --- a/testing/symbolic/scalar_constraint_test.go +++ b/testing/symbolic/scalar_constraint_test.go @@ -941,3 +941,319 @@ func TestScalarConstraint_LinearEqualityConstraintRepresentation5(t *testing.T) ) } } + +/* +TestScalarConstraint_Substitute1 +Description: + + This tests that the ScalarConstraint.Substitute() method properly panics + when the left hand side is not a valid monomial. +*/ +func TestScalarConstraint_Substitute1(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.Substitute() to panic; received nil", + ) + } + + rAsError := r.(error) + expectedError := m1.Check() + if rAsError.Error() != expectedError.Error() { + t.Errorf( + "Expected sc.Substitute() to panic with error \"%v\"; received \"%v\"", + expectedError.Error(), + rAsError.Error(), + ) + } + }() + + // Call the method + sc.Substitute(v2, symbolic.K(1)) + + t.Errorf( + "Expected sc.Substitute() to panic; received nil", + ) +} + +/* +TestScalarConstraint_Substitute2 +Description: + + This tests that the ScalarConstraint.Substitute() method properly + returns a new ScalarConstraint when the left hand side is a valid monomial. + In this case, we substitute a variable for a sum of two variables, + which should lead to a new constraint with the same sense. +*/ +func TestScalarConstraint_Substitute2(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, + } + + // Substitute + sum := y.Plus(x) + sumAsSE, ok := sum.(symbolic.ScalarExpression) + if !ok { + t.Errorf( + "Expected sum to be a symbolic.ScalarExpression; received %T", + sum, + ) + } + + newSc := sc.Substitute(x, sumAsSE) + + // Verify that the new left hand side is a polynomial and NOT a monomial + if _, ok := newSc.Left().(symbolic.Monomial); ok { + t.Errorf( + "Expected newSc.LeftHandSide to be a symbolic.Polynomial; received %T", + newSc.Left(), + ) + } + + if _, ok := newSc.Left().(symbolic.Polynomial); !ok { + t.Errorf( + "Expected newSc.LeftHandSide to be a symbolic.Polynomial; received %T", + newSc.Left(), + ) + } + + // Verify that the new right hand side is a constant 3.14 + 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 3.14 + if float64(m2) != 3.14 { + t.Errorf( + "Expected newSc.RightHandSide to be 3.14; 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_SubstituteAccordingTo1 +Description: + + This tests that the ScalarConstraint.SubstituteAccordingTo() method + properly panics when the left hand side is not a valid monomial. +*/ +func TestScalarConstraint_SubstituteAccordingTo1(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.SubstituteAccordingTo() to panic; received nil", + ) + } + + rAsError := r.(error) + expectedError := m1.Check() + if rAsError.Error() != expectedError.Error() { + t.Errorf( + "Expected sc.SubstituteAccordingTo() to panic with error \"%v\"; received \"%v\"", + expectedError.Error(), + rAsError.Error(), + ) + } + }() + + sc.SubstituteAccordingTo(nil) + + t.Errorf( + "Expected sc.SubstituteAccordingTo() to panic; received nil", + ) +} + +/* +TestScalarConstraint_SubstituteAccordingTo2 +Description: + + This tests that the ScalarConstraint.SubstituteAccordingTo() method + properly returns a new ScalarConstraint when the left hand side is a valid monomial. + In this case, we substitute a variable for a sum of two variables, + which should lead to a new constraint with the same sense. +*/ +func TestScalarConstraint_SubstituteAccordingTo2(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, + } + + // Substitute + sum := y.Plus(x) + sumAsSE, ok := sum.(symbolic.ScalarExpression) + if !ok { + t.Errorf( + "Expected sum to be a symbolic.ScalarExpression; received %T", + sum, + ) + } + + newSc := sc.SubstituteAccordingTo( + map[symbolic.Variable]symbolic.Expression{ + x: sumAsSE, + }, + ) + + // Verify that the new left hand side is a polynomial and NOT a monomial + if _, ok := newSc.Left().(symbolic.Monomial); ok { + t.Errorf( + "Expected newSc.LeftHandSide to be a symbolic.Polynomial; received %T", + newSc.Left(), + ) + } + + if _, ok := newSc.Left().(symbolic.Polynomial); !ok { + t.Errorf( + "Expected newSc.LeftHandSide to be a symbolic.Polynomial; received %T", + newSc.Left(), + ) + } + + // Verify that the new right hand side is a constant 3.14 + m2, ok := newSc.Right().(symbolic.K) + if !ok { + t.Errorf( + "Expected newSc.RightHandSide to be a symbolic.K; received %T", + newSc.Right(), + ) + } + + if float64(m2) != 3.14 { + t.Errorf( + "Expected newSc.RightHandSide to be 3.14; received %v", + m2, + ) + } + + if newSc.ConstrSense() != sc.Sense { + t.Errorf( + "Expected newSc.Sense to be %v; received %v", + sc.Sense, + newSc.ConstrSense(), + ) + } +} + +/* +TestScalarConstraint_String1 +Description: + + Tests the String() method of a scalar constraint. This test verifies + that the method properly returns a string representation of the + constraint. + We will double check that the string contains + - the left hand side's string representation + - the right hand side's string representation + - the sense of the constraint +*/ +func TestScalarConstraint_String1(t *testing.T) { + // Constants + x := symbolic.NewVariable() + c2 := symbolic.K(3.14) + + // Create constraint + sc := symbolic.ScalarConstraint{x, c2, symbolic.SenseLessThanEqual} + + // Get string representation + s := sc.String() + + // Verify that the string contains the left hand side + if !strings.Contains(s, sc.Left().String()) { + t.Errorf( + "Expected s to contain %v; received %v", + sc.Left().String(), + s, + ) + } + + // Verify that the string contains the right hand side + if !strings.Contains(s, sc.Right().String()) { + t.Errorf( + "Expected s to contain %v; received %v", + sc.Right().String(), + s, + ) + } + + // Verify that the string contains the sense + if !strings.Contains(s, sc.ConstrSense().String()) { + t.Errorf( + "Expected s to contain %v; received %v", + sc.ConstrSense().String(), + s, + ) + } +} diff --git a/testing/symbolic/vector_constraint_test.go b/testing/symbolic/vector_constraint_test.go index 5fedfb4..2fee4d9 100644 --- a/testing/symbolic/vector_constraint_test.go +++ b/testing/symbolic/vector_constraint_test.go @@ -819,3 +819,224 @@ func TestVectorConstraint_LinearEqualityConstraintRepresentation5(t *testing.T) vc.LinearEqualityConstraintRepresentation() } + +/* +TestVectorConstraint_Substitute1 +Description: + + This function tests that the Substitute method properly panics + if the input scalar expression is not well defined. + (In this case, the left hand side is a monomial that is not well-defined.) +*/ +func TestVectorConstraint_Substitute1(t *testing.T) { + // Setup + N := 7 + x := symbolic.NewVariable() + left := symbolic.VecDenseToKVector(symbolic.OnesVector(N)).ToMonomialVector() + left[6] = symbolic.Monomial{ + Coefficient: 1, + Exponents: []int{1, 0}, + VariableFactors: []symbolic.Variable{x}, + } + right := symbolic.NewVariableVector(N) + + // 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.Substitute() to panic; received nil", + ) + } + + rAsError := r.(error) + expectedError := left[6].Check() + if !strings.Contains(rAsError.Error(), expectedError.Error()) { + t.Errorf( + "Expected vc.Substitute() to panic with error \"%v\"; received \"%v\"", + expectedError.Error(), + rAsError.Error(), + ) + } + }() + + // Test + vc.Substitute(x, x.Power(3).(symbolic.ScalarExpression)) + + // Raise an error if the test did not panic + t.Errorf( + "Expected vc.Substitute() to panic; received nil", + ) + +} + +/* +TestVectorConstraint_Substitute2 +Description: + + This function tests that the Substitute method properly returns + a new vector constraint when the input scalar expressions are well-defined. + In this case, we will start with a vector of monomials on the left and + a vector of constants on the right. + We will substitute a variable on the left hand side with a polynomial + and expect the result to be a vector of polynomials on the left hand side + and a vector of constants on the right hand side. +*/ +func TestVectorConstraint_Substitute2(t *testing.T) { + // Setup + N := 7 + x := symbolic.NewVariableVector(N) + left := x.ToMonomialVector() + right := symbolic.VecDenseToKVector(symbolic.OnesVector(N)) + + // Create the vector constraint + vc := symbolic.VectorConstraint{left, right, symbolic.SenseLessThanEqual} + + // Create the new expression to replace one of the variables with: + newExpr := x[0].Plus(x[1]).(symbolic.ScalarExpression) + + // Substitute the variable + newVC := vc.Substitute(x[0], newExpr).(symbolic.VectorConstraint) + + // Check the left hand side + // it should now be a vector of polynomials + if _, ok := newVC.LeftHandSide.(symbolic.PolynomialVector); !ok { + t.Errorf( + "Expected vc.Substitute() to return a vector constraint with a polynomial vector on the left hand side; received %v", + newVC.LeftHandSide, + ) + } + + // Check the right hand side + if _, ok := newVC.RightHandSide.(symbolic.KVector); !ok { + t.Errorf( + "Expected vc.Substitute() to return a vector constraint with a constant vector on the right hand side; received %v", + newVC.RightHandSide, + ) + } + + // Check the sense + if newVC.Sense != vc.Sense { + t.Errorf( + "Expected vc.Substitute() to return a vector constraint with the same sense; received %v", + newVC.Sense, + ) + } + +} + +/* +TestVectorConstraint_SubstituteAccordingTo1 +Description: + + This function tests that the SubstituteAccordingTo method properly panics + if the input scalar expression is not well defined. + (In this case, the left hand side is a monomial that is not well-defined.) +*/ +func TestVectorConstraint_SubstituteAccordingTo1(t *testing.T) { + // Setup + N := 7 + x := symbolic.NewVariable() + left := symbolic.VecDenseToKVector(symbolic.OnesVector(N)).ToMonomialVector() + left[6] = symbolic.Monomial{ + Coefficient: 1, + Exponents: []int{1, 0}, + VariableFactors: []symbolic.Variable{x}, + } + right := symbolic.NewVariableVector(N) + + // 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.SubstituteAccordingTo() to panic; received nil", + ) + } + + rAsError := r.(error) + expectedError := left[6].Check() + if !strings.Contains(rAsError.Error(), expectedError.Error()) { + t.Errorf( + "Expected vc.SubstituteAccordingTo() to panic with error \"%v\"; received \"%v\"", + expectedError.Error(), + rAsError.Error(), + ) + } + }() + + // Test + subMap := map[symbolic.Variable]symbolic.Expression{ + x: x.Power(3), + } + vc.SubstituteAccordingTo(subMap) + + // Raise an error if the test did not panic + t.Errorf( + "Expected vc.SubstituteAccordingTo() to panic; received nil", + ) + +} + +/* +TestVectorConstraint_SubstituteAccordingTo2 +Description: + + This function tests that the SubstituteAccordingTo method properly returns + a new vector constraint when the input scalar expressions are well-defined. + In this case, we will start with a vector of monomials on the left and + a vector of constants on the right. + We will substitute a variable on the left hand side with a polynomial + and expect the result to be a vector of polynomials on the left hand side + and a vector of constants on the right hand side. +*/ +func TestVectorConstraint_SubstituteAccordingTo2(t *testing.T) { + // Setup + N := 7 + x := symbolic.NewVariableVector(N) + left := x.ToMonomialVector() + right := symbolic.VecDenseToKVector(symbolic.OnesVector(N)) + + // Create the vector constraint + vc := symbolic.VectorConstraint{left, right, symbolic.SenseLessThanEqual} + + // Create the new expression to replace one of the variables with: + newExpr := x[0].Plus(x[1]).(symbolic.ScalarExpression) + + // Substitute the variable + subMap := map[symbolic.Variable]symbolic.Expression{ + x[0]: newExpr, + } + newVC := vc.SubstituteAccordingTo(subMap).(symbolic.VectorConstraint) + + // Check the left hand side + // it should now be a vector of polynomials + if _, ok := newVC.LeftHandSide.(symbolic.PolynomialVector); !ok { + t.Errorf( + "Expected vc.SubstituteAccordingTo() to return a vector constraint with a polynomial vector on the left hand side; received %v", + newVC.LeftHandSide, + ) + } + + // Check the right hand side + if _, ok := newVC.RightHandSide.(symbolic.KVector); !ok { + t.Errorf( + "Expected vc.SubstituteAccordingTo() to return a vector constraint with a constant vector on the right hand side; received %v", + newVC.RightHandSide, + ) + } + + // Check the sense + if newVC.Sense != vc.Sense { + t.Errorf( + "Expected vc.SubstituteAccordingTo() to return a vector constraint with the same sense; received %v", + newVC.Sense, + ) + } +}