From e6a13fc5b8112bdd544a91222807c4876701536b Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Tue, 12 Aug 2025 17:39:06 -0700 Subject: [PATCH 1/6] Small formatting concern --- symbolic/constr_sense.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/symbolic/constr_sense.go b/symbolic/constr_sense.go index e34ee70..75d4bc0 100644 --- a/symbolic/constr_sense.go +++ b/symbolic/constr_sense.go @@ -10,8 +10,8 @@ type ConstrSense byte // Different constraint senses conforming to Gurobi's encoding. const ( SenseEqual ConstrSense = '=' - SenseLessThanEqual = '<' - SenseGreaterThanEqual = '>' + SenseLessThanEqual ConstrSense = '<' + SenseGreaterThanEqual ConstrSense = '>' ) func (cs ConstrSense) String() string { From 4f014780264dceaaa3f342664e8e2953782fcabb Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Tue, 12 Aug 2025 17:39:39 -0700 Subject: [PATCH 2/6] Fixed comments + added new method for detecting non-negativity constraints --- symbolic/scalar_constraint.go | 59 ++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/symbolic/scalar_constraint.go b/symbolic/scalar_constraint.go index 70f860e..99447d8 100644 --- a/symbolic/scalar_constraint.go +++ b/symbolic/scalar_constraint.go @@ -285,7 +285,7 @@ func (sc ScalarConstraint) String() string { } /* -Simplify +AsSimplifiedConstraint Description: Simplifies the constraint by moving all variables to the left hand side and the constants to the right. @@ -445,3 +445,60 @@ func (sc ScalarConstraint) ImpliesThisIsAlsoSatisfied(other Constraint) bool { return false } + +/* +IsNonnegativityConstraint +Description: + + Checks to see if the constraint is of the form: + - x >= 0, or + - 0 <= x +*/ +func (sc ScalarConstraint) IsNonnegativityConstraint() bool { + // Setup + err := sc.Check() + if err != nil { + panic(err) + } + + simplified := sc.AsSimplifiedConstraint().(ScalarConstraint) + + // Check to see if constraint contains more than 1 variable + if len(simplified.Variables()) != 1 { + return false + } + + // If the SIMPLIFIED constraint is an equality OR LessThanEqual constraint, + // Then it can not be a non-negativity constraint. + if simplified.Sense != SenseGreaterThanEqual { + return false + } + + // Otherwise, the sense is SenseGreaterThanEqual, and this is a non-negativity + // constraint if: + // - LHS is Variable-Like + // - RHS is Zero + + lhsIsVariableLike := false + + // LHS Is Variable Like if: + // - It is a variable + // - It is a monomial with a positive coefficient + _, tf := simplified.LeftHandSide.(Variable) + lhsIsVariableLike = lhsIsVariableLike || tf + + if !lhsIsVariableLike { + if monom, tf := simplified.LeftHandSide.(Monomial); tf { + lhsIsVariableLike = lhsIsVariableLike || (monom.Coefficient > 0) + } + } + + // Check to see if rhs is zero + rhsAsK := simplified.RightHandSide.(K) + rhsIsZero := float64(rhsAsK) == 0 + + // Return true, if: + // - LHS is variable-like, AND + // - RHS is zero. + return lhsIsVariableLike && rhsIsZero +} From 66a423a3f609110b14758f8f938346d6337c380f Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Sat, 23 Aug 2025 14:59:35 -0400 Subject: [PATCH 3/6] Introduced a new method for deconstructing constraints into ScalarConstraint objects --- symbolic/constraint.go | 47 +++++++++++++++++++++++++++++ testing/symbolic/constraint_test.go | 29 ++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/symbolic/constraint.go b/symbolic/constraint.go index 2caa3c2..e8a7e7f 100644 --- a/symbolic/constraint.go +++ b/symbolic/constraint.go @@ -1,5 +1,7 @@ package symbolic +import "fmt" + /* constraint.go Description: @@ -27,6 +29,10 @@ type Constraint interface { // AsSimplifiedConstraint // Simplifies the constraint by moving all variables to the left hand side and the constants to the right. AsSimplifiedConstraint() Constraint + + // // IsPositivityConstraint + // // Returns true if the constraint defines a non-negativity constraint of the form x >= 0 + // IsNonnegativityConstraint() bool } func IsConstraint(c interface{}) bool { @@ -83,3 +89,44 @@ func VariablesInThisConstraint(c Constraint) []Variable { return vars } + +/* +CompileConstraintsIntoScalarConstraints +Description: + + This method analyzes all constraints in an OptimizationProblem and converts them all + into scalar constraints. +*/ +func CompileConstraintsIntoScalarConstraints(constraints []Constraint) []ScalarConstraint { + // Setup + var out []ScalarConstraint + + // Iterate through all constraints + for _, constraint := range constraints { + // Switch statement based on the type of the constraint + switch concreteConstraint := constraint.(type) { + case ScalarConstraint: + out = append(out, concreteConstraint) + case VectorConstraint: + for ii := 0; ii < concreteConstraint.Len(); ii++ { + out = append(out, concreteConstraint.AtVec(ii)) + } + case MatrixConstraint: + dims := concreteConstraint.Dims() + for rowIdx := 0; rowIdx < dims[0]; rowIdx++ { + for colIdx := 0; colIdx < dims[1]; colIdx++ { + out = append(out, concreteConstraint.At(rowIdx, colIdx)) + } + } + default: + panic( + fmt.Errorf( + "The received constraint type (%T) is not supported by ExtractScalarConstraints!", + constraint, + ), + ) + } + } + + return out +} diff --git a/testing/symbolic/constraint_test.go b/testing/symbolic/constraint_test.go index 359d0db..98d1773 100644 --- a/testing/symbolic/constraint_test.go +++ b/testing/symbolic/constraint_test.go @@ -216,3 +216,32 @@ func TestConstraint_VariablesInThisConstraint1(t *testing.T) { ) } } + +/* +TestConstraint_CompileConstraintsIntoScalarConstraints1 +Description: + + This test verifies that a single vector constraint + of length 3 is transformed into 3 constraints. +*/ +func TestConstraint_CompileConstraintsIntoScalarConstraints1(t *testing.T) { + // Setup + N := 3 + x := symbolic.NewVariableVector(N) + + // Create Vector Inequality Constraint + vectorConstraint := x.LessEq(symbolic.OnesVector(N)) + + // Decompose + constraints := symbolic.CompileConstraintsIntoScalarConstraints( + []symbolic.Constraint{vectorConstraint}, + ) + if len(constraints) != N { + t.Errorf( + "Expected to find %v ScalarConstraints; discovered %v", + N, + len(constraints), + ) + } + +} From e01fcf736d8d1dced414353a17524b457ee7034e Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Fri, 29 Aug 2025 13:56:33 -0400 Subject: [PATCH 4/6] Added more support for integer inputs to symbolic methods + added tests for IsNonnegativityConstraint method --- symbolic/constant_vector.go | 14 ++ symbolic/monomial.go | 4 + symbolic/polynomial.go | 2 + symbolic/scalar_constraint.go | 33 +-- symbolic/variable.go | 3 + testing/symbolic/scalar_constraint_test.go | 232 +++++++++++++++++++++ 6 files changed, 273 insertions(+), 15 deletions(-) diff --git a/symbolic/constant_vector.go b/symbolic/constant_vector.go index a2d36f1..7714c91 100644 --- a/symbolic/constant_vector.go +++ b/symbolic/constant_vector.go @@ -307,6 +307,15 @@ func (kv KVector) Comparison(rightIn interface{}, sense ConstrSense) Constraint } switch rhsConverted := rightIn.(type) { + case float64: + var rhsAsVector *mat.VecDense + onesVector := OnesVector(kv.Len()) + rhsAsVector.ScaleVec(rhsConverted, &onesVector) + return kv.Comparison(rhsAsVector, sense) + case int: + return kv.Comparison(float64(rhsConverted), sense) + case K: + return kv.Comparison(float64(rhsConverted), sense) case mat.VecDense: // Use KVector's Comparison method return kv.Comparison(VecDenseToKVector(rhsConverted), sense) @@ -612,6 +621,11 @@ func (kv KVector) ToPolynomialVector() PolynomialVector { return kv.ToMonomialVector().ToPolynomialVector() } +/* +ToKMatrix +Description: +*/ + /* Degree Description: diff --git a/symbolic/monomial.go b/symbolic/monomial.go index ac1440b..07b7eb1 100644 --- a/symbolic/monomial.go +++ b/symbolic/monomial.go @@ -201,6 +201,8 @@ func (m Monomial) Multiply(e interface{}) Expression { switch right := e.(type) { case float64: return m.Multiply(K(right)) + case int: + return m.Multiply(K(float64(right))) case K: rightAsFloat64 := float64(right) monomialOut := m @@ -339,6 +341,8 @@ func (m Monomial) Comparison(rhsIn interface{}, sense ConstrSense) Constraint { switch right := rhsIn.(type) { case float64: return m.Comparison(K(right), sense) + case int: + return m.Comparison(K(float64(right)), sense) case K: return ScalarConstraint{m, right, sense} case Variable: diff --git a/symbolic/polynomial.go b/symbolic/polynomial.go index 9bd2538..9e2b3a7 100644 --- a/symbolic/polynomial.go +++ b/symbolic/polynomial.go @@ -466,6 +466,8 @@ func (p Polynomial) Comparison(rightIn interface{}, sense ConstrSense) Constrain switch right := rightIn.(type) { case float64: return p.Comparison(K(right), sense) + case int: + return p.Comparison(float64(right), sense) case K: return ScalarConstraint{p, right, sense} case Variable: diff --git a/symbolic/scalar_constraint.go b/symbolic/scalar_constraint.go index 99447d8..6177ea1 100644 --- a/symbolic/scalar_constraint.go +++ b/symbolic/scalar_constraint.go @@ -468,12 +468,6 @@ func (sc ScalarConstraint) IsNonnegativityConstraint() bool { return false } - // If the SIMPLIFIED constraint is an equality OR LessThanEqual constraint, - // Then it can not be a non-negativity constraint. - if simplified.Sense != SenseGreaterThanEqual { - return false - } - // Otherwise, the sense is SenseGreaterThanEqual, and this is a non-negativity // constraint if: // - LHS is Variable-Like @@ -484,21 +478,30 @@ func (sc ScalarConstraint) IsNonnegativityConstraint() bool { // LHS Is Variable Like if: // - It is a variable // - It is a monomial with a positive coefficient - _, tf := simplified.LeftHandSide.(Variable) - lhsIsVariableLike = lhsIsVariableLike || tf + simplifiedAsPL, tf := simplified.LeftHandSide.(PolynomialLikeScalar) + if !tf { + return false // If lhs is not polynomial like, then return false. + } + + lhsIsVariableLike = simplifiedAsPL.Degree() == 1 if !lhsIsVariableLike { - if monom, tf := simplified.LeftHandSide.(Monomial); tf { - lhsIsVariableLike = lhsIsVariableLike || (monom.Coefficient > 0) - } + return false // If lhs is still not variable like, then return false. } // Check to see if rhs is zero rhsAsK := simplified.RightHandSide.(K) rhsIsZero := float64(rhsAsK) == 0 - // Return true, if: - // - LHS is variable-like, AND - // - RHS is zero. - return lhsIsVariableLike && rhsIsZero + if !rhsIsZero { + return false + } + + // Finally, the constraint is non-negativie if: + // - LHS has positive coefficient AND sense is GreaterThanEqual + // - LHS has negative coefficient AND sense is LessThanEqual + coeffs := simplified.LeftHandSide.LinearCoeff() + + return (coeffs.AtVec(0) > 0 && simplified.Sense == SenseGreaterThanEqual) || + (coeffs.AtVec(0) < 0 && simplified.Sense == SenseLessThanEqual) } diff --git a/symbolic/variable.go b/symbolic/variable.go index 9aad657..5188659 100644 --- a/symbolic/variable.go +++ b/symbolic/variable.go @@ -232,6 +232,9 @@ func (v Variable) Comparison(rhsIn interface{}, sense ConstrSense) Constraint { case float64: // Use version of comparison for K return v.Comparison(K(rhs), sense) + case int: + // Use version of comparison for K + return v.Comparison(K(float64(rhs)), sense) case K: // Create a new constraint return ScalarConstraint{v, rhs, sense} diff --git a/testing/symbolic/scalar_constraint_test.go b/testing/symbolic/scalar_constraint_test.go index 9b5238d..2dee798 100644 --- a/testing/symbolic/scalar_constraint_test.go +++ b/testing/symbolic/scalar_constraint_test.go @@ -2000,3 +2000,235 @@ func TestScalarConstraint_AsSimplifiedConstraint1(t *testing.T) { ) } } + +/* +TestScalarConstraint_IsNonnegativityConstraint1 +Description: + + Tests the IsNonnegativityConstraint() method of a scalar constraint. + This test verifies that the method correctly identifies a non-negativity + constraint of the form: x >= 0. +*/ +func TestScalarConstraint_IsNonnegativityConstraint1(t *testing.T) { + // Constants + x := symbolic.NewVariable() + + // Create constraint + constr := x.GreaterEq(0) + sc, ok := constr.(symbolic.ScalarConstraint) + if !ok { + t.Errorf( + "Expected constr to be a symbolic.ScalarConstraint; received %T", + constr, + ) + } + + // Verify that the constraint is identified as a non-negativity constraint + if !sc.IsNonnegativityConstraint() { + t.Errorf( + "Expected sc.IsNonnegativityConstraint() to be true; received false", + ) + } +} + +/* +TestScalarConstraint_IsNonnegativityConstraint2 +Description: + + This test verifies that the new method panics if the + input ScalarConstraint is not well-defined. +*/ +func TestScalarConstraint_IsNonnegativityConstraint2(t *testing.T) { + // Setup + x := symbolic.NewVariable() + + // Create a malformed monomial + m1 := symbolic.Monomial{ + Coefficient: 2, + Exponents: []int{1, 1}, + VariableFactors: []symbolic.Variable{x}, + } + + // Create constraint + sc := symbolic.ScalarConstraint{ + LeftHandSide: m1, + RightHandSide: symbolic.K(0), + Sense: symbolic.SenseGreaterThanEqual, + } + + // Verify that the IsNonnegativityConstraint() method + // panics + defer func() { + r := recover() + if r == nil { + t.Errorf( + "Expected IsNonnegativityConstraint() to panic; received no panic", + ) + } + + err, ok := r.(error) + if !ok { + t.Errorf( + "Expected IsNonnegativityConstraint() to panic with an error; received %v", + r, + ) + } + + if err.Error() != m1.Check().Error() { + t.Errorf( + "Expected IsNonnegativityConstraint() to panic with error %v; received %v", + m1.Check().Error(), + err.Error(), + ) + } + }() + + sc.IsNonnegativityConstraint() + t.Errorf("Expected IsNonnegativityConstraint() to panic; received no panic") +} + +/* +TestScalarConstraint_IsNonnegativityConstraint3 +Description: + + This test verifies that the IsNonnegativityConstraint() method correctly + returns false when the scalar constraint contains more than one variable. +*/ +func TestScalarConstraint_IsNonnegativityConstraint3(t *testing.T) { + // Constants + x := symbolic.NewVariable() + y := symbolic.NewVariable() + + // Create constraint + constr := x.Plus(y).GreaterEq(0) + sc, ok := constr.(symbolic.ScalarConstraint) + if !ok { + t.Errorf( + "Expected constr to be a symbolic.ScalarConstraint; received %T", + constr, + ) + } + + // Verify that the constraint is identified as NOT a non-negativity constraint + if sc.IsNonnegativityConstraint() { + t.Errorf( + "Expected sc.IsNonnegativityConstraint() to be false; received true", + ) + } +} + +/* +TestScalarConstraint_IsNonnegativityConstraint4 +Description: + + This test verifies that the IsNonnegativityConstraint() method correctly identifies + that a non-positivity constraint (i.e., x <= 0) is NOT a non-negativity constraint. +*/ +func TestScalarConstraint_IsNonnegativityConstraint4(t *testing.T) { + // Constants + x := symbolic.NewVariable() + + // Create constraint + constr := x.LessEq(0) + sc, ok := constr.(symbolic.ScalarConstraint) + if !ok { + t.Errorf( + "Expected constr to be a symbolic.ScalarConstraint; received %T", + constr, + ) + } + + // Verify that the constraint is identified as NOT a non-negativity constraint + if sc.IsNonnegativityConstraint() { + t.Errorf( + "Expected sc.IsNonnegativityConstraint() to be false; received true", + ) + } +} + +/* +TestScalarConstraint_IsNonnegativityConstraint5 +Description: + + This test verifies that the IsNonnegativityConstraint() method correctly identifies + a constraint of the form 2 * x >= 0 IS ALSO A non-negativity constraint. +*/ +func TestScalarConstraint_IsNonnegativityConstraint5(t *testing.T) { + // Constants + x := symbolic.NewVariable() + + // Create constraint + constr := symbolic.K(2).Multiply(x).GreaterEq(0) + sc, ok := constr.(symbolic.ScalarConstraint) + if !ok { + t.Errorf( + "Expected constr to be a symbolic.ScalarConstraint; received %T", + constr, + ) + } + + // Verify that the constraint is identified as a non-negativity constraint + if !sc.IsNonnegativityConstraint() { + t.Errorf( + "Expected sc.IsNonnegativityConstraint() to be true; received false", + ) + } +} + +/* +TestScalarConstraint_IsNonnegativityConstraint6 +Description: + + This test verifies that the IsNonnegativityConstraint() method correctly + identifies that a constraint of the form x >= 3 is NOT a non-negativity constraint. +*/ +func TestScalarConstraint_IsNonnegativityConstraint6(t *testing.T) { + // Constants + x := symbolic.NewVariable() + + // Create constraint + constr := x.GreaterEq(3) + sc, ok := constr.(symbolic.ScalarConstraint) + if !ok { + t.Errorf( + "Expected constr to be a symbolic.ScalarConstraint; received %T", + constr, + ) + } + + // Verify that the constraint is identified as NOT a non-negativity constraint + if sc.IsNonnegativityConstraint() { + t.Errorf( + "Expected sc.IsNonnegativityConstraint() to be false; received true", + ) + } +} + +/* +TestScalarConstraint_IsNonnegativityConstraint7 +Description: + + This test verifies that the IsNonnegativityConstraint() method correctly identifies + that a constraint of the form - x <= 0 IS a non-negativity constraint. +*/ +func TestScalarConstraint_IsNonnegativityConstraint7(t *testing.T) { + // Constants + x := symbolic.NewVariable() + + // Create constraint + constr := symbolic.K(-1).Multiply(x).LessEq(0) + sc, ok := constr.(symbolic.ScalarConstraint) + if !ok { + t.Errorf( + "Expected constr to be a symbolic.ScalarConstraint; received %T", + constr, + ) + } + + // Verify that the constraint is identified as a non-negativity constraint + if !sc.IsNonnegativityConstraint() { + t.Errorf( + "Expected sc.IsNonnegativityConstraint() to be true; received false", + ) + } +} From f87180123b33126a347c7d2484e3be72462ad305 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Fri, 29 Aug 2025 14:26:07 -0400 Subject: [PATCH 5/6] Adding tests for constant_vector's new comparison inputs --- symbolic/constant_vector.go | 2 +- testing/symbolic/constant_vector_test.go | 148 +++++++++++++++++++++++ testing/symbolic/constraint_test.go | 39 ++++++ 3 files changed, 188 insertions(+), 1 deletion(-) diff --git a/symbolic/constant_vector.go b/symbolic/constant_vector.go index 7714c91..63414f2 100644 --- a/symbolic/constant_vector.go +++ b/symbolic/constant_vector.go @@ -308,7 +308,7 @@ func (kv KVector) Comparison(rightIn interface{}, sense ConstrSense) Constraint switch rhsConverted := rightIn.(type) { case float64: - var rhsAsVector *mat.VecDense + var rhsAsVector mat.VecDense onesVector := OnesVector(kv.Len()) rhsAsVector.ScaleVec(rhsConverted, &onesVector) return kv.Comparison(rhsAsVector, sense) diff --git a/testing/symbolic/constant_vector_test.go b/testing/symbolic/constant_vector_test.go index 9a77da8..96a5cf8 100644 --- a/testing/symbolic/constant_vector_test.go +++ b/testing/symbolic/constant_vector_test.go @@ -720,6 +720,154 @@ func TestConstantVector_Comparison1(t *testing.T) { kv1.Comparison(input, symbolic.SenseEqual) } +/* +TestConstantVector_Comparison2 +Description: + + Verifies that the Comparison method produces a proper + constraint when the left-hand side is a KVector and + the right-hand side is a float64. + The resulting compmarison should have a right hand + side which is a KVector. +*/ +func TestConstantVector_Comparison2(t *testing.T) { + // Constants + kv1 := symbolic.VecDenseToKVector(symbolic.OnesVector(3)) + var input float64 = 3.14 + + // Test + constraint := kv1.Comparison(input, symbolic.SenseEqual) + + // Verify that the left hand side is of type KVector + if _, tf := constraint.Left().(symbolic.KVector); !tf { + t.Errorf( + "Expected constraint.LeftHandSide to be of type KVector; received %v", + constraint.Left(), + ) + } + + // Verify that the right hand side is of type KVector + if _, tf := constraint.Right().(symbolic.KVector); !tf { + t.Errorf( + "Expected constraint.RightHandSide to be of type KVector; received %v", + constraint.Right(), + ) + } + + // Verify that the sense of the constraint is Equal + if constraint.ConstrSense() != symbolic.SenseEqual { + t.Errorf( + "Expected constraint.Sense to be Equal; received %v", + constraint.ConstrSense(), + ) + } +} + +/* +TestConstantVector_Comparison3 +Description: + + Verifies that the Comparison method produces a proper + constraint when the left-hand side is a KVector and + the right-hand side is a int. + The resulting compmarison should have a right hand + side which is a KVector. +*/ +func TestConstantVector_Comparison3(t *testing.T) { + // Constants + N := 3 + kv1 := symbolic.VecDenseToKVector(symbolic.OnesVector(N)) + var input int = 3 + + // Test + constraint := kv1.Comparison(input, symbolic.SenseEqual) + + // Verify that the left hand side is of type KVector + if _, tf := constraint.Left().(symbolic.KVector); !tf { + t.Errorf( + "Expected constraint.LeftHandSide to be of type KVector; received %v", + constraint.Left(), + ) + } + + // Verify that the right hand side is of type KVector + kv, tf := constraint.Right().(symbolic.KVector) + if !tf { + t.Errorf( + "Expected constraint.RightHandSide to be of type KVector; received %v", + constraint.Right(), + ) + } + + // Verify that the length of the right hand side is N + if kv.Len() != N { + t.Errorf( + "Expected constraint.RightHandSide to have length %d; received %d", + N, kv.Len(), + ) + } + + // Verify that the sense of the constraint is Equal + if constraint.ConstrSense() != symbolic.SenseEqual { + t.Errorf( + "Expected constraint.Sense to be Equal; received %v", + constraint.ConstrSense(), + ) + } +} + +/* +TestConstantVector_Comparison4 +Description: + + Verifies that the Comparison method produces a proper + constraint when the left-hand side is a KVector and + the right-hand side is a symbolic.K. + The resulting compmarison should have a right hand + side which is a KVector. +*/ +func TestConstantVector_Comparison4(t *testing.T) { + // Constants + N := 3 + kv1 := symbolic.VecDenseToKVector(symbolic.OnesVector(N)) + var input symbolic.K = symbolic.K(3.0) + + // Test + constraint := kv1.Comparison(input, symbolic.SenseEqual) + + // Verify that the left hand side is of type KVector + if _, tf := constraint.Left().(symbolic.KVector); !tf { + t.Errorf( + "Expected constraint.LeftHandSide to be of type KVector; received %v", + constraint.Left(), + ) + } + + // Verify that the right hand side is of type KVector and has length N + kv, tf := constraint.Right().(symbolic.KVector) + if !tf { + t.Errorf( + "Expected constraint.RightHandSide to be of type KVector; received %v", + constraint.Right(), + ) + } + + if kv.Len() != N { + t.Errorf( + "Expected constraint.RightHandSide to have length %d; received %d", + N, kv.Len(), + ) + } + + // Verify that the sense of the constraint is Equal + if constraint.ConstrSense() != symbolic.SenseEqual { + t.Errorf( + "Expected constraint.Sense to be Equal; received %v", + constraint.ConstrSense(), + ) + } +} + /* TestConstantVector_Multiply1 Description: diff --git a/testing/symbolic/constraint_test.go b/testing/symbolic/constraint_test.go index 98d1773..3e94bcd 100644 --- a/testing/symbolic/constraint_test.go +++ b/testing/symbolic/constraint_test.go @@ -245,3 +245,42 @@ func TestConstraint_CompileConstraintsIntoScalarConstraints1(t *testing.T) { } } + +/* +TestConstraint_CompileConstraintsIntoScalarConstraints2 +Description: + + This test verifies that compiling a slice of: + - one ScalarConstraint + - one matrix constraint of size 4x4, and + - one vector constraint of length 2 + gets compiled into 19 = 1 + 16 + 2. +*/ +func TestConstraint_CompileConstraintsIntoScalarConstraints2(t *testing.T) { + // Setup + N := 4 + x := symbolic.NewVariableMatrix(N, N) + M := 2 + y := symbolic.NewVariableVector(M) + z := symbolic.NewVariable() + + // Create Matrix Inequality Constraint + matrixConstraint := x.LessEq(symbolic.OnesMatrix(N, N)) + + // Create Vector Inequality Constraint + vectorConstraint := y.LessEq(symbolic.OnesVector(M)) + + // Decompose + inputConstraints := []symbolic.Constraint{matrixConstraint, vectorConstraint, z.LessEq(-3.0)} + constraints := symbolic.CompileConstraintsIntoScalarConstraints( + inputConstraints, + ) + if len(constraints) != N*N+M+1 { + t.Errorf( + "Expected to find %v ScalarConstraints; discovered %v", + N*N+M+1, + len(constraints), + ) + } + +} From e3c0252b749ce59ebf2d16065873b60b8227b533 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Fri, 29 Aug 2025 14:28:22 -0400 Subject: [PATCH 6/6] Added test for corner case of non-negativity (power of 2 for single variable comparison) --- testing/symbolic/scalar_constraint_test.go | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/testing/symbolic/scalar_constraint_test.go b/testing/symbolic/scalar_constraint_test.go index 2dee798..6321d33 100644 --- a/testing/symbolic/scalar_constraint_test.go +++ b/testing/symbolic/scalar_constraint_test.go @@ -2232,3 +2232,32 @@ func TestScalarConstraint_IsNonnegativityConstraint7(t *testing.T) { ) } } + +/* +TestScalarConstraint_IsNonnegativityConstraint8 +Description: + + This test verifies that the IsNonnegativityConstraint() method correctly identifies + that a constraint of the form x^2 >= 0 IS NOT a non-negativity constraint. +*/ +func TestScalarConstraint_IsNonnegativityConstraint8(t *testing.T) { + // Constants + x := symbolic.NewVariable() + + // Create constraint + constr := x.Power(2).GreaterEq(0) + sc, ok := constr.(symbolic.ScalarConstraint) + if !ok { + t.Errorf( + "Expected constr to be a symbolic.ScalarConstraint; received %T", + constr, + ) + } + + // Verify that the constraint is identified as NOT a non-negativity constraint + if sc.IsNonnegativityConstraint() { + t.Errorf( + "Expected sc.IsNonnegativityConstraint() to be false; received true", + ) + } +}