From b8c16dce650836772f3070771fa72dd4d132a0bf Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Sat, 3 May 2025 14:22:51 -0400 Subject: [PATCH 01/12] Added the Substitute() and SubstituteAccordingTo() methods for constraints to simplify some operations --- symbolic/constraint.go | 2 ++ symbolic/matrix_constraint.go | 46 ++++++++++++++++++++++++++++ symbolic/scalar_constraint.go | 56 +++++++++++++++++++++++++++++++++++ symbolic/vector_constraint.go | 42 ++++++++++++++++++++++++++ 4 files changed, 146 insertions(+) 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/scalar_constraint.go b/symbolic/scalar_constraint.go index 751a94e..1483fff 100644 --- a/symbolic/scalar_constraint.go +++ b/symbolic/scalar_constraint.go @@ -208,3 +208,59 @@ 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, + } +} diff --git a/symbolic/vector_constraint.go b/symbolic/vector_constraint.go index 1167025..3143eb0 100644 --- a/symbolic/vector_constraint.go +++ b/symbolic/vector_constraint.go @@ -257,3 +257,45 @@ 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} +} From b05d6fa853e5cfa79fca36d386c1bb1ad8c76ac0 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Sat, 3 May 2025 18:50:47 -0400 Subject: [PATCH 02/12] Upgraded the gonum dependency --- go.mod | 6 ++++-- go.sum | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) 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..56756d5 100644 --- a/go.sum +++ b/go.sum @@ -2,3 +2,5 @@ golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAb 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= From 3c4262db154191ccf1f3a3bfde2bf514b12b81e8 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Sat, 3 May 2025 19:51:57 -0400 Subject: [PATCH 03/12] Introducing Len() function for vector constraints --- symbolic/vector_constraint.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/symbolic/vector_constraint.go b/symbolic/vector_constraint.go index 3143eb0..1bd8bea 100644 --- a/symbolic/vector_constraint.go +++ b/symbolic/vector_constraint.go @@ -299,3 +299,20 @@ func (vc VectorConstraint) SubstituteAccordingTo(subMap map[Variable]Expression) // 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() +} From e7913b666c284159b24b29a450ae415830c7965b Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Sat, 3 May 2025 20:23:20 -0400 Subject: [PATCH 04/12] Updating github action to use newer version of Go --- .github/workflows/coverage1.yml | 2 +- go.sum | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) 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.sum b/go.sum index 56756d5..83477d1 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +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= From d13348a701dee4a983120a23fa4bc17aba622bd8 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Mon, 12 May 2025 23:20:57 -0400 Subject: [PATCH 05/12] Added tests for ScalarConstraint's Substitute method --- testing/symbolic/scalar_constraint_test.go | 132 +++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/testing/symbolic/scalar_constraint_test.go b/testing/symbolic/scalar_constraint_test.go index 9c1f217..994c3b7 100644 --- a/testing/symbolic/scalar_constraint_test.go +++ b/testing/symbolic/scalar_constraint_test.go @@ -941,3 +941,135 @@ 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(), + ) + } +} From f9a41431fd347d7aa2bf1e955d3c26dfa858e195 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Mon, 12 May 2025 23:30:55 -0400 Subject: [PATCH 06/12] Fixed subtle bug in monomial's substitute according to method --- symbolic/monomial.go | 8 +- testing/symbolic/scalar_constraint_test.go | 137 +++++++++++++++++++++ 2 files changed, 142 insertions(+), 3 deletions(-) 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/testing/symbolic/scalar_constraint_test.go b/testing/symbolic/scalar_constraint_test.go index 994c3b7..82e137b 100644 --- a/testing/symbolic/scalar_constraint_test.go +++ b/testing/symbolic/scalar_constraint_test.go @@ -1073,3 +1073,140 @@ func TestScalarConstraint_Substitute2(t *testing.T) { ) } } + +/* +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, + }, + ) + t.Errorf( + "newSc = %v", + newSc, + ) + + // 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(), + ) + } +} From d712b8d0e22e0fce8b716753a7a2e74397aaae46 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Mon, 12 May 2025 23:31:07 -0400 Subject: [PATCH 07/12] Introduced string() method to constraint --- symbolic/scalar_constraint.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/symbolic/scalar_constraint.go b/symbolic/scalar_constraint.go index 1483fff..18cffbb 100644 --- a/symbolic/scalar_constraint.go +++ b/symbolic/scalar_constraint.go @@ -264,3 +264,20 @@ func (sc ScalarConstraint) SubstituteAccordingTo(subMap map[Variable]Expression) 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() +} From 182144243b3559f503b3a5a2aeb3386146907be5 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Mon, 12 May 2025 23:31:43 -0400 Subject: [PATCH 08/12] Removing debugging statement --- testing/symbolic/scalar_constraint_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/testing/symbolic/scalar_constraint_test.go b/testing/symbolic/scalar_constraint_test.go index 82e137b..de06b69 100644 --- a/testing/symbolic/scalar_constraint_test.go +++ b/testing/symbolic/scalar_constraint_test.go @@ -1166,10 +1166,6 @@ func TestScalarConstraint_SubstituteAccordingTo2(t *testing.T) { x: sumAsSE, }, ) - t.Errorf( - "newSc = %v", - newSc, - ) // Verify that the new left hand side is a polynomial and NOT a monomial if _, ok := newSc.Left().(symbolic.Monomial); ok { From b673b933c2af7390c9ea9c079321628328f2b3ed Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Mon, 12 May 2025 23:42:08 -0400 Subject: [PATCH 09/12] Add in tests for VectorConstraint.Substitute() --- testing/symbolic/vector_constraint_test.go | 108 +++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/testing/symbolic/vector_constraint_test.go b/testing/symbolic/vector_constraint_test.go index 5fedfb4..e2fab78 100644 --- a/testing/symbolic/vector_constraint_test.go +++ b/testing/symbolic/vector_constraint_test.go @@ -819,3 +819,111 @@ 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, + ) + } + +} From ac36532c7b013997c63409d0fd949cf75d6cfd4b Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Mon, 12 May 2025 23:51:44 -0400 Subject: [PATCH 10/12] Adding tests for VectorConstraint.SubstituteAccordingTo --- testing/symbolic/vector_constraint_test.go | 113 +++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/testing/symbolic/vector_constraint_test.go b/testing/symbolic/vector_constraint_test.go index e2fab78..2fee4d9 100644 --- a/testing/symbolic/vector_constraint_test.go +++ b/testing/symbolic/vector_constraint_test.go @@ -927,3 +927,116 @@ func TestVectorConstraint_Substitute2(t *testing.T) { } } + +/* +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, + ) + } +} From c37016cd58c6e0feb01a28d5891e05949f9e452d Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Mon, 12 May 2025 23:52:59 -0400 Subject: [PATCH 11/12] Added small test for ScalarConstraint.String method --- testing/symbolic/scalar_constraint_test.go | 51 ++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/testing/symbolic/scalar_constraint_test.go b/testing/symbolic/scalar_constraint_test.go index de06b69..7343aca 100644 --- a/testing/symbolic/scalar_constraint_test.go +++ b/testing/symbolic/scalar_constraint_test.go @@ -1206,3 +1206,54 @@ func TestScalarConstraint_SubstituteAccordingTo2(t *testing.T) { ) } } + +/* +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, + ) + } +} From 85db6dc00fdb717b6c32dc343f22a43f486aeb77 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Mon, 12 May 2025 23:57:01 -0400 Subject: [PATCH 12/12] Added tests for MatrixConstraint.Substitute and MatrixConstraint.SubstituteAccordingTo --- testing/symbolic/matrix_constraint_test.go | 211 ++++++++++++++++++++- 1 file changed, 210 insertions(+), 1 deletion(-) 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, + ) + } +}