From d344ab43a12034d19e0d5f7abfaddf2119ace1d9 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Thu, 17 Apr 2025 21:20:24 -0400 Subject: [PATCH 1/2] Fixed bug in how scalar constraint computes linear constraint representations (make sure that we always include the wrt variable) --- symbolic/polynomial_like_vector.go | 2 +- symbolic/scalar_constraint.go | 4 +- testing/symbolic/scalar_constraint_test.go | 94 ++++++++++++++++++++++ 3 files changed, 97 insertions(+), 3 deletions(-) diff --git a/symbolic/polynomial_like_vector.go b/symbolic/polynomial_like_vector.go index 414f97c..0cfbe36 100644 --- a/symbolic/polynomial_like_vector.go +++ b/symbolic/polynomial_like_vector.go @@ -30,7 +30,7 @@ type PolynomialLikeVector interface { // Variables returns the number of variables in the expression. Variables() []Variable - // Coeffs returns a slice of the coefficients in the expression + // LinearCoeff returns a slice of the coefficients in the expression LinearCoeff(wrt ...[]Variable) mat.Dense // Constant returns the constant additive value in the expression diff --git a/symbolic/scalar_constraint.go b/symbolic/scalar_constraint.go index 0f0fb6f..751a94e 100644 --- a/symbolic/scalar_constraint.go +++ b/symbolic/scalar_constraint.go @@ -137,7 +137,7 @@ func (sc ScalarConstraint) LinearInequalityConstraintRepresentation(wrt ...[]Var newLHS := sc.Left().(ScalarExpression) newLHS = newLHS.Minus(sc.Right()).(ScalarExpression) - A = newLHS.LinearCoeff() + A = newLHS.LinearCoeff(wrt...) if sc.Sense == SenseGreaterThanEqual { A.ScaleVec(-1, &A) @@ -199,7 +199,7 @@ func (sc ScalarConstraint) LinearEqualityConstraintRepresentation(wrt ...[]Varia // Create C newLHS := sc.Left().(ScalarExpression) newLHS = newLHS.Minus(sc.Right()).(ScalarExpression) - C = newLHS.LinearCoeff() + C = newLHS.LinearCoeff(wrt...) // Create d newRHS := sc.Right().(ScalarExpression).Constant() - sc.Left().(ScalarExpression).Constant() diff --git a/testing/symbolic/scalar_constraint_test.go b/testing/symbolic/scalar_constraint_test.go index 7f7dc04..9c1f217 100644 --- a/testing/symbolic/scalar_constraint_test.go +++ b/testing/symbolic/scalar_constraint_test.go @@ -652,6 +652,53 @@ func TestScalarConstraint_LinearInequalityConstraintRepresentation5(t *testing.T sc.(symbolic.ScalarConstraint).LinearInequalityConstraintRepresentation() } +/* +TestScalarConstraint_LinearInequalityConstraintRepresentation6 +Description: + + Tests the LinearInequalityConstraintRepresentation() method of a scalar + constraint. This test verifies that the method correctly produces a vector with + length 2 and a constant of value 2.1 when using a small cosntraint: + x1 <= 2.1 + but when calculating the representation with respect to a vector of 2 variables. +*/ +func TestScalarConstraint_LinearInequalityConstraintRepresentation6(t *testing.T) { + // Constants + x := symbolic.NewVariableVector(2) + c2 := symbolic.K(2.1) + + // Create constraint + sc := x.AtVec(0).LessEq(c2) + + // Verify that the constraint is linear + if !sc.IsLinear() { + t.Errorf( + "Expected sc to be linear; received %v", + sc.IsLinear(), + ) + } + + // Get linear representation + A, b := sc.(symbolic.ScalarConstraint).LinearInequalityConstraintRepresentation(x) + + // Verify that the vector is all ones + if A.AtVec(0) != 1 { + t.Errorf("Expected A[0] to be 1; received %v", A.AtVec(0)) + } + + if A.AtVec(1) != 0 { + t.Errorf("Expected A[1] to be 0; received %v", A.AtVec(1)) + } + + // Verify that the constant is 2.5 + if b != 2.1 { + t.Errorf( + "Expected b to be 2.5; received %v", + b, + ) + } +} + /* TestScalarConstraint_LinearEqualityConstraintRepresentation1 Description: @@ -847,3 +894,50 @@ func TestScalarConstraint_LinearEqualityConstraintRepresentation4(t *testing.T) sc.(symbolic.ScalarConstraint).LinearEqualityConstraintRepresentation() } + +/* +TestScalarConstraint_LinearEqualityConstraintRepresentation5 +Description: + + Tests the LinearEqualityConstraintRepresentation() method of a scalar + constraint. This test verifies that the method correctly produces a vector with + length 2 and a constant of value 2.1 when using a small cosntraint: + x1 = 2.1 + but when calculating the representation with respect to a vector of 2 variables. +*/ +func TestScalarConstraint_LinearEqualityConstraintRepresentation5(t *testing.T) { + // Constants + x := symbolic.NewVariableVector(2) + c2 := symbolic.K(2.1) + + // Create constraint + sc := x.AtVec(0).Eq(c2) + + // Verify that the constraint is linear + if !sc.IsLinear() { + t.Errorf( + "Expected sc to be linear; received %v", + sc.IsLinear(), + ) + } + + // Get linear representation + A, b := sc.(symbolic.ScalarConstraint).LinearEqualityConstraintRepresentation(x) + + // Verify that the vector is all ones + if A.AtVec(0) != 1 { + t.Errorf("Expected A[0] to be 1; received %v", A.AtVec(0)) + } + + if A.AtVec(1) != 0 { + t.Errorf("Expected A[1] to be 0; received %v", A.AtVec(1)) + } + + // Verify that the constant is 2.5 + if b != 2.1 { + t.Errorf( + "Expected b to be 2.5; received %v", + b, + ) + } +} From 02ea95a1a66f886f49a72a6e102305fdc0af4d0e Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Thu, 17 Apr 2025 21:29:07 -0400 Subject: [PATCH 2/2] Fixed a few more cases that were needed to make the bug's tests work --- symbolic/constant_vector.go | 16 ++++---- symbolic/variable.go | 3 ++ symbolic/vector_expression.go | 4 +- testing/symbolic/vector_constraint_test.go | 46 ++++++++++++++++++++++ 4 files changed, 60 insertions(+), 9 deletions(-) diff --git a/symbolic/constant_vector.go b/symbolic/constant_vector.go index b90086b..a2d36f1 100644 --- a/symbolic/constant_vector.go +++ b/symbolic/constant_vector.go @@ -160,19 +160,18 @@ func (kv KVector) Plus(rightIn interface{}) Expression { // Add the values return kv.Plus(VecDenseToKVector(eAsVec)) - case K: - // Return Addition - return kv.Plus(float64(right)) - case Variable: + case K, Variable, Monomial, Polynomial: // Create a new polynomial vector - var pvOut PolynomialVector + var out []ScalarExpression for _, element := range kv { - pvOut = append(pvOut, element.Plus(right).(Polynomial)) + out = append(out, element.Plus(right).(ScalarExpression)) } - return pvOut + return ConcretizeVectorExpression(out) case *mat.VecDense: return kv.Plus(VecDenseToKVector(*right)) // Convert to KVector + case mat.VecDense: + return kv.Plus(VecDenseToKVector(right)) // Convert to KVector case KVector: // Compute Addition @@ -234,6 +233,9 @@ func (kv KVector) Minus(e interface{}) Expression { return kv.Minus(VecDenseToKVector(right)) // Convert to KVector case *mat.VecDense: return kv.Minus(VecDenseToKVector(*right)) // Convert to KVector + case K, Variable, Monomial, Polynomial: + rightAsSE := right.(ScalarExpression) + return kv.Plus(rightAsSE.Multiply(-1.0)) // Reuse K case case KVector, VariableVector, MonomialVector, PolynomialVector: // Force the right hand side to be a VectorExpression rhsAsVE := right.(VectorExpression) diff --git a/symbolic/variable.go b/symbolic/variable.go index 20bd91a..5c888f0 100644 --- a/symbolic/variable.go +++ b/symbolic/variable.go @@ -121,6 +121,9 @@ func (v Variable) Plus(rightIn interface{}) Expression { return right.Plus(v) case *mat.VecDense: return v.Plus(VecDenseToKVector(*right)) + case mat.VecDense: + // Convert to KVector + return v.Plus(VecDenseToKVector(right)) case KVector, VariableVector, MonomialVector, PolynomialVector: ve, _ := ToVectorExpression(rightIn) return ve.Plus(v) diff --git a/symbolic/vector_expression.go b/symbolic/vector_expression.go index 1d0d833..4b1309d 100644 --- a/symbolic/vector_expression.go +++ b/symbolic/vector_expression.go @@ -30,8 +30,8 @@ type VectorExpression interface { // Variables returns the number of variables in the expression. Variables() []Variable - //// Coeffs returns a slice of the coefficients in the expression - //LinearCoeff() mat.Dense + // LinearCoeffs returns a slice of the coefficients in the expression + LinearCoeff(wrt ...[]Variable) mat.Dense // Constant returns the constant additive value in the expression Constant() mat.VecDense diff --git a/testing/symbolic/vector_constraint_test.go b/testing/symbolic/vector_constraint_test.go index 60f35d7..5fedfb4 100644 --- a/testing/symbolic/vector_constraint_test.go +++ b/testing/symbolic/vector_constraint_test.go @@ -564,6 +564,52 @@ func TestVectorConstraint_LinearInequalityConstraintRepresentation7(t *testing.T vc.LinearInequalityConstraintRepresentation() } +/* +TestVectorConstraint_LinearInequalityConstraintRepresentation8 +Description: + + This function tests that the LinearInequalityConstraintRepresentation method + properly returns a matrix of shape (2, 2) and a vector of shape (2, 1) + for a well-defined, lienar vector constraint. This constraint will only contain + 1 variable, but the w.r.t. variable will contain 2 variables. +*/ +func TestVectorConstraint_LinearInequalityConstraintRepresentation8(t *testing.T) { + // Constants + N := 2 + x := symbolic.NewVariableVector(N) + left := x.AtVec(0).Plus(symbolic.ZerosVector(N)) + right := mat.NewVecDense(N, []float64{1, 2}) + vc := left.LessEq(right).(symbolic.VectorConstraint) + + // Test + A, b := vc.LinearInequalityConstraintRepresentation(x) + + nRowsA, nColsA := A.Dims() + if nRowsA != N || nColsA != 2 { + t.Errorf( + "Expected vc.LinearInequalityConstraintRepresentation() to return a matrix of dimension %v; received dimension (%v, %v)", + []int{N, 2}, + nRowsA, nColsA, + ) + } + + if b.AtVec(0) != 1 { + t.Errorf( + "Expected vc.LinearEqualityConstraintRepresentation()'s b vector to contain a 1 at the %v-th index; received %v", + 0, + b.AtVec(0), + ) + } + + if b.AtVec(1) != 2 { + t.Errorf( + "Expected vc.LinearEqualityConstraintRepresentation()'s b vector to contain a 2 at the %v-th index; received %v", + 1, + b.AtVec(1), + ) + } +} + /* TestVectorConstraint_LinearEqualityConstraintRepresentation1 Description: