Skip to content

Commit 9adff8d

Browse files
kwesiRutledgeKwesi Rutledge
andauthored
Introducing Ability to Detect Non-Negativity Constraints + Some Helper Methods for Breaking Down Large Constraints Into Smaller Ones (#20)
* Small formatting concern * Fixed comments + added new method for detecting non-negativity constraints * Introduced a new method for deconstructing constraints into ScalarConstraint objects * Added more support for integer inputs to symbolic methods + added tests for IsNonnegativityConstraint method * Adding tests for constant_vector's new comparison inputs * Added test for corner case of non-negativity (power of 2 for single variable comparison) --------- Co-authored-by: Kwesi Rutledge <[email protected]>
1 parent d38793d commit 9adff8d

File tree

10 files changed

+610
-3
lines changed

10 files changed

+610
-3
lines changed

symbolic/constant_vector.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,15 @@ func (kv KVector) Comparison(rightIn interface{}, sense ConstrSense) Constraint
307307
}
308308

309309
switch rhsConverted := rightIn.(type) {
310+
case float64:
311+
var rhsAsVector mat.VecDense
312+
onesVector := OnesVector(kv.Len())
313+
rhsAsVector.ScaleVec(rhsConverted, &onesVector)
314+
return kv.Comparison(rhsAsVector, sense)
315+
case int:
316+
return kv.Comparison(float64(rhsConverted), sense)
317+
case K:
318+
return kv.Comparison(float64(rhsConverted), sense)
310319
case mat.VecDense:
311320
// Use KVector's Comparison method
312321
return kv.Comparison(VecDenseToKVector(rhsConverted), sense)
@@ -612,6 +621,11 @@ func (kv KVector) ToPolynomialVector() PolynomialVector {
612621
return kv.ToMonomialVector().ToPolynomialVector()
613622
}
614623

624+
/*
625+
ToKMatrix
626+
Description:
627+
*/
628+
615629
/*
616630
Degree
617631
Description:

symbolic/constr_sense.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ type ConstrSense byte
1010
// Different constraint senses conforming to Gurobi's encoding.
1111
const (
1212
SenseEqual ConstrSense = '='
13-
SenseLessThanEqual = '<'
14-
SenseGreaterThanEqual = '>'
13+
SenseLessThanEqual ConstrSense = '<'
14+
SenseGreaterThanEqual ConstrSense = '>'
1515
)
1616

1717
func (cs ConstrSense) String() string {

symbolic/constraint.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package symbolic
22

3+
import "fmt"
4+
35
/*
46
constraint.go
57
Description:
@@ -27,6 +29,10 @@ type Constraint interface {
2729
// AsSimplifiedConstraint
2830
// Simplifies the constraint by moving all variables to the left hand side and the constants to the right.
2931
AsSimplifiedConstraint() Constraint
32+
33+
// // IsPositivityConstraint
34+
// // Returns true if the constraint defines a non-negativity constraint of the form x >= 0
35+
// IsNonnegativityConstraint() bool
3036
}
3137

3238
func IsConstraint(c interface{}) bool {
@@ -83,3 +89,44 @@ func VariablesInThisConstraint(c Constraint) []Variable {
8389

8490
return vars
8591
}
92+
93+
/*
94+
CompileConstraintsIntoScalarConstraints
95+
Description:
96+
97+
This method analyzes all constraints in an OptimizationProblem and converts them all
98+
into scalar constraints.
99+
*/
100+
func CompileConstraintsIntoScalarConstraints(constraints []Constraint) []ScalarConstraint {
101+
// Setup
102+
var out []ScalarConstraint
103+
104+
// Iterate through all constraints
105+
for _, constraint := range constraints {
106+
// Switch statement based on the type of the constraint
107+
switch concreteConstraint := constraint.(type) {
108+
case ScalarConstraint:
109+
out = append(out, concreteConstraint)
110+
case VectorConstraint:
111+
for ii := 0; ii < concreteConstraint.Len(); ii++ {
112+
out = append(out, concreteConstraint.AtVec(ii))
113+
}
114+
case MatrixConstraint:
115+
dims := concreteConstraint.Dims()
116+
for rowIdx := 0; rowIdx < dims[0]; rowIdx++ {
117+
for colIdx := 0; colIdx < dims[1]; colIdx++ {
118+
out = append(out, concreteConstraint.At(rowIdx, colIdx))
119+
}
120+
}
121+
default:
122+
panic(
123+
fmt.Errorf(
124+
"The received constraint type (%T) is not supported by ExtractScalarConstraints!",
125+
constraint,
126+
),
127+
)
128+
}
129+
}
130+
131+
return out
132+
}

symbolic/monomial.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@ func (m Monomial) Multiply(e interface{}) Expression {
201201
switch right := e.(type) {
202202
case float64:
203203
return m.Multiply(K(right))
204+
case int:
205+
return m.Multiply(K(float64(right)))
204206
case K:
205207
rightAsFloat64 := float64(right)
206208
monomialOut := m
@@ -339,6 +341,8 @@ func (m Monomial) Comparison(rhsIn interface{}, sense ConstrSense) Constraint {
339341
switch right := rhsIn.(type) {
340342
case float64:
341343
return m.Comparison(K(right), sense)
344+
case int:
345+
return m.Comparison(K(float64(right)), sense)
342346
case K:
343347
return ScalarConstraint{m, right, sense}
344348
case Variable:

symbolic/polynomial.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,8 @@ func (p Polynomial) Comparison(rightIn interface{}, sense ConstrSense) Constrain
466466
switch right := rightIn.(type) {
467467
case float64:
468468
return p.Comparison(K(right), sense)
469+
case int:
470+
return p.Comparison(float64(right), sense)
469471
case K:
470472
return ScalarConstraint{p, right, sense}
471473
case Variable:

symbolic/scalar_constraint.go

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ func (sc ScalarConstraint) String() string {
285285
}
286286

287287
/*
288-
Simplify
288+
AsSimplifiedConstraint
289289
Description:
290290
291291
Simplifies the constraint by moving all variables to the left hand side and the constants to the right.
@@ -445,3 +445,63 @@ func (sc ScalarConstraint) ImpliesThisIsAlsoSatisfied(other Constraint) bool {
445445

446446
return false
447447
}
448+
449+
/*
450+
IsNonnegativityConstraint
451+
Description:
452+
453+
Checks to see if the constraint is of the form:
454+
- x >= 0, or
455+
- 0 <= x
456+
*/
457+
func (sc ScalarConstraint) IsNonnegativityConstraint() bool {
458+
// Setup
459+
err := sc.Check()
460+
if err != nil {
461+
panic(err)
462+
}
463+
464+
simplified := sc.AsSimplifiedConstraint().(ScalarConstraint)
465+
466+
// Check to see if constraint contains more than 1 variable
467+
if len(simplified.Variables()) != 1 {
468+
return false
469+
}
470+
471+
// Otherwise, the sense is SenseGreaterThanEqual, and this is a non-negativity
472+
// constraint if:
473+
// - LHS is Variable-Like
474+
// - RHS is Zero
475+
476+
lhsIsVariableLike := false
477+
478+
// LHS Is Variable Like if:
479+
// - It is a variable
480+
// - It is a monomial with a positive coefficient
481+
simplifiedAsPL, tf := simplified.LeftHandSide.(PolynomialLikeScalar)
482+
if !tf {
483+
return false // If lhs is not polynomial like, then return false.
484+
}
485+
486+
lhsIsVariableLike = simplifiedAsPL.Degree() == 1
487+
488+
if !lhsIsVariableLike {
489+
return false // If lhs is still not variable like, then return false.
490+
}
491+
492+
// Check to see if rhs is zero
493+
rhsAsK := simplified.RightHandSide.(K)
494+
rhsIsZero := float64(rhsAsK) == 0
495+
496+
if !rhsIsZero {
497+
return false
498+
}
499+
500+
// Finally, the constraint is non-negativie if:
501+
// - LHS has positive coefficient AND sense is GreaterThanEqual
502+
// - LHS has negative coefficient AND sense is LessThanEqual
503+
coeffs := simplified.LeftHandSide.LinearCoeff()
504+
505+
return (coeffs.AtVec(0) > 0 && simplified.Sense == SenseGreaterThanEqual) ||
506+
(coeffs.AtVec(0) < 0 && simplified.Sense == SenseLessThanEqual)
507+
}

symbolic/variable.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,9 @@ func (v Variable) Comparison(rhsIn interface{}, sense ConstrSense) Constraint {
232232
case float64:
233233
// Use version of comparison for K
234234
return v.Comparison(K(rhs), sense)
235+
case int:
236+
// Use version of comparison for K
237+
return v.Comparison(K(float64(rhs)), sense)
235238
case K:
236239
// Create a new constraint
237240
return ScalarConstraint{v, rhs, sense}

testing/symbolic/constant_vector_test.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,154 @@ func TestConstantVector_Comparison1(t *testing.T) {
720720
kv1.Comparison(input, symbolic.SenseEqual)
721721
}
722722

723+
/*
724+
TestConstantVector_Comparison2
725+
Description:
726+
727+
Verifies that the Comparison method produces a proper
728+
constraint when the left-hand side is a KVector and
729+
the right-hand side is a float64.
730+
The resulting compmarison should have a right hand
731+
side which is a KVector.
732+
*/
733+
func TestConstantVector_Comparison2(t *testing.T) {
734+
// Constants
735+
kv1 := symbolic.VecDenseToKVector(symbolic.OnesVector(3))
736+
var input float64 = 3.14
737+
738+
// Test
739+
constraint := kv1.Comparison(input, symbolic.SenseEqual)
740+
741+
// Verify that the left hand side is of type KVector
742+
if _, tf := constraint.Left().(symbolic.KVector); !tf {
743+
t.Errorf(
744+
"Expected constraint.LeftHandSide to be of type KVector; received %v",
745+
constraint.Left(),
746+
)
747+
}
748+
749+
// Verify that the right hand side is of type KVector
750+
if _, tf := constraint.Right().(symbolic.KVector); !tf {
751+
t.Errorf(
752+
"Expected constraint.RightHandSide to be of type KVector; received %v",
753+
constraint.Right(),
754+
)
755+
}
756+
757+
// Verify that the sense of the constraint is Equal
758+
if constraint.ConstrSense() != symbolic.SenseEqual {
759+
t.Errorf(
760+
"Expected constraint.Sense to be Equal; received %v",
761+
constraint.ConstrSense(),
762+
)
763+
}
764+
}
765+
766+
/*
767+
TestConstantVector_Comparison3
768+
Description:
769+
770+
Verifies that the Comparison method produces a proper
771+
constraint when the left-hand side is a KVector and
772+
the right-hand side is a int.
773+
The resulting compmarison should have a right hand
774+
side which is a KVector.
775+
*/
776+
func TestConstantVector_Comparison3(t *testing.T) {
777+
// Constants
778+
N := 3
779+
kv1 := symbolic.VecDenseToKVector(symbolic.OnesVector(N))
780+
var input int = 3
781+
782+
// Test
783+
constraint := kv1.Comparison(input, symbolic.SenseEqual)
784+
785+
// Verify that the left hand side is of type KVector
786+
if _, tf := constraint.Left().(symbolic.KVector); !tf {
787+
t.Errorf(
788+
"Expected constraint.LeftHandSide to be of type KVector; received %v",
789+
constraint.Left(),
790+
)
791+
}
792+
793+
// Verify that the right hand side is of type KVector
794+
kv, tf := constraint.Right().(symbolic.KVector)
795+
if !tf {
796+
t.Errorf(
797+
"Expected constraint.RightHandSide to be of type KVector; received %v",
798+
constraint.Right(),
799+
)
800+
}
801+
802+
// Verify that the length of the right hand side is N
803+
if kv.Len() != N {
804+
t.Errorf(
805+
"Expected constraint.RightHandSide to have length %d; received %d",
806+
N, kv.Len(),
807+
)
808+
}
809+
810+
// Verify that the sense of the constraint is Equal
811+
if constraint.ConstrSense() != symbolic.SenseEqual {
812+
t.Errorf(
813+
"Expected constraint.Sense to be Equal; received %v",
814+
constraint.ConstrSense(),
815+
)
816+
}
817+
}
818+
819+
/*
820+
TestConstantVector_Comparison4
821+
Description:
822+
823+
Verifies that the Comparison method produces a proper
824+
constraint when the left-hand side is a KVector and
825+
the right-hand side is a symbolic.K.
826+
The resulting compmarison should have a right hand
827+
side which is a KVector.
828+
*/
829+
func TestConstantVector_Comparison4(t *testing.T) {
830+
// Constants
831+
N := 3
832+
kv1 := symbolic.VecDenseToKVector(symbolic.OnesVector(N))
833+
var input symbolic.K = symbolic.K(3.0)
834+
835+
// Test
836+
constraint := kv1.Comparison(input, symbolic.SenseEqual)
837+
838+
// Verify that the left hand side is of type KVector
839+
if _, tf := constraint.Left().(symbolic.KVector); !tf {
840+
t.Errorf(
841+
"Expected constraint.LeftHandSide to be of type KVector; received %v",
842+
constraint.Left(),
843+
)
844+
}
845+
846+
// Verify that the right hand side is of type KVector and has length N
847+
kv, tf := constraint.Right().(symbolic.KVector)
848+
if !tf {
849+
t.Errorf(
850+
"Expected constraint.RightHandSide to be of type KVector; received %v",
851+
constraint.Right(),
852+
)
853+
}
854+
855+
if kv.Len() != N {
856+
t.Errorf(
857+
"Expected constraint.RightHandSide to have length %d; received %d",
858+
N, kv.Len(),
859+
)
860+
}
861+
862+
// Verify that the sense of the constraint is Equal
863+
if constraint.ConstrSense() != symbolic.SenseEqual {
864+
t.Errorf(
865+
"Expected constraint.Sense to be Equal; received %v",
866+
constraint.ConstrSense(),
867+
)
868+
}
869+
}
870+
723871
/*
724872
TestConstantVector_Multiply1
725873
Description:

0 commit comments

Comments
 (0)