Skip to content

Fix Bug in How LinearConstraint Representations handle w.r.t. variables for scalar case #17

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions symbolic/constant_vector.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,19 +160,18 @@

// 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

Check warning on line 174 in symbolic/constant_vector.go

View check run for this annotation

Codecov / codecov/patch

symbolic/constant_vector.go#L173-L174

Added lines #L173 - L174 were not covered by tests

case KVector:
// Compute Addition
Expand Down Expand Up @@ -234,6 +233,9 @@
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

Check warning on line 238 in symbolic/constant_vector.go

View check run for this annotation

Codecov / codecov/patch

symbolic/constant_vector.go#L236-L238

Added lines #L236 - L238 were not covered by tests
case KVector, VariableVector, MonomialVector, PolynomialVector:
// Force the right hand side to be a VectorExpression
rhsAsVE := right.(VectorExpression)
Expand Down
2 changes: 1 addition & 1 deletion symbolic/polynomial_like_vector.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions symbolic/scalar_constraint.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand Down
3 changes: 3 additions & 0 deletions symbolic/variable.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions symbolic/vector_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
94 changes: 94 additions & 0 deletions testing/symbolic/scalar_constraint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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,
)
}
}
46 changes: 46 additions & 0 deletions testing/symbolic/vector_constraint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down