Skip to content

Commit d38793d

Browse files
kwesiRutledgeKwesi Rutledge
andauthored
Introducing A Few More Methods for Handling Constraints (#19)
* Introduced a few new methods for constraints * Added a few more tests to increase coverage of the implication method for ScalarConstraint * Adding a test for VariablesInThisConstraint to increase coverage * Added more tests for coverage of new functions * Added more tests to increase coverage of the VectorConstraint methods * Fixed noticeable bug in ImpliesThisIsAlsoSatisfied * Added test for ImpliesThisIsAlsoSatisfied * Testing naive implication check in Implies This Is Also Satisfied for VectorConstraint * Added todos for other constraint types * Condensing some of the TODO sections --------- Co-authored-by: Kwesi Rutledge <[email protected]>
1 parent 4aba36b commit d38793d

9 files changed

+1764
-1
lines changed

symbolic/constraint.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@ type Constraint interface {
1515
IsLinear() bool
1616
Substitute(vIn Variable, seIn ScalarExpression) Constraint
1717
SubstituteAccordingTo(subMap map[Variable]Expression) Constraint
18+
19+
// Variables
20+
// Returns a slice of all the variables in the constraint.
21+
Variables() []Variable
22+
23+
// ImpliesThisIsAlsoSatisfied
24+
// Returns true if this constraint implies that the other constraint is also satisfied.
25+
ImpliesThisIsAlsoSatisfied(other Constraint) bool
26+
27+
// AsSimplifiedConstraint
28+
// Simplifies the constraint by moving all variables to the left hand side and the constants to the right.
29+
AsSimplifiedConstraint() Constraint
1830
}
1931

2032
func IsConstraint(c interface{}) bool {
@@ -36,3 +48,38 @@ func IsConstraint(c interface{}) bool {
3648
// Return false, if the constraint is not a scalar or vector constraint.
3749
return false
3850
}
51+
52+
/*
53+
Variables
54+
Description:
55+
56+
Returns a slice of all the variables in the constraint.
57+
*/
58+
func VariablesInThisConstraint(c Constraint) []Variable {
59+
// Setup
60+
varsMap := make(map[Variable]bool)
61+
62+
// Input check
63+
err := c.Check()
64+
if err != nil {
65+
panic(err)
66+
}
67+
68+
// Get variables from the left hand side
69+
for _, v := range c.Left().Variables() {
70+
varsMap[v] = true
71+
}
72+
73+
// Get variables from the right hand side
74+
for _, v := range c.Right().Variables() {
75+
varsMap[v] = true
76+
}
77+
78+
// Convert the map to a slice
79+
vars := make([]Variable, 0, len(varsMap))
80+
for v := range varsMap {
81+
vars = append(vars, v)
82+
}
83+
84+
return vars
85+
}

symbolic/matrix_constraint.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,84 @@ func (mc MatrixConstraint) SubstituteAccordingTo(subMap map[Variable]Expression)
191191

192192
return MatrixConstraint{newLHS, newRHS, mc.Sense}
193193
}
194+
195+
/*
196+
AsSimplifiedConstraint
197+
Description:
198+
199+
Simplifies the constraint by moving all variables to the left hand side and the constants to the right.
200+
*/
201+
func (mc MatrixConstraint) AsSimplifiedConstraint() Constraint {
202+
// Create Left Hand side of all of the expressions
203+
var newLHS Expression = mc.LeftHandSide.Minus(mc.LeftHandSide.Constant())
204+
newLHS = newLHS.Minus(
205+
mc.RightHandSide.Minus(mc.RightHandSide.Constant()),
206+
)
207+
208+
// Create Right Hand Side of only constants
209+
var newRHS Expression = DenseToKMatrix(mc.RightHandSide.Constant()).Minus(
210+
mc.LeftHandSide.Constant(),
211+
)
212+
213+
// Return new constraint
214+
return MatrixConstraint{
215+
LeftHandSide: newLHS.(MatrixExpression),
216+
RightHandSide: newRHS.(MatrixExpression),
217+
Sense: mc.Sense,
218+
}
219+
}
220+
221+
/*
222+
Variables
223+
Description:
224+
225+
Returns a slice of all the variables in the constraint.
226+
*/
227+
func (mc MatrixConstraint) Variables() []Variable {
228+
return VariablesInThisConstraint(mc)
229+
}
230+
231+
/*
232+
ImpliesThisIsAlsoSatisfied
233+
Description:
234+
235+
Returns true if this constraint implies that the other constraint is also satisfied.
236+
*/
237+
func (mc MatrixConstraint) ImpliesThisIsAlsoSatisfied(other Constraint) bool {
238+
// Input Processing
239+
err := mc.Check()
240+
if err != nil {
241+
panic(err)
242+
}
243+
244+
err = other.Check()
245+
if err != nil {
246+
panic(err)
247+
}
248+
249+
// Implication Avenues
250+
switch otherC := other.(type) {
251+
case ScalarConstraint:
252+
// If the other constraint is a scalar constraint,
253+
// then it can only be implied if:
254+
// - one of the elements of the matrix constraint implies the scalar constraint.
255+
for i := 0; i < mc.Dims()[0]; i++ {
256+
for j := 0; j < mc.Dims()[1]; j++ {
257+
if mc.At(i, j).ImpliesThisIsAlsoSatisfied(otherC) {
258+
return true
259+
}
260+
}
261+
}
262+
case VectorConstraint, MatrixConstraint:
263+
// TODO: Implement more advanced implication checks.
264+
return false
265+
default:
266+
// Other types of constraints are not currently supported.
267+
panic(
268+
fmt.Errorf("implication checking between MatrixConstraint and %T is not currently supported", other),
269+
)
270+
}
271+
272+
// If no avenues for implication were found, return false.
273+
return false
274+
}

symbolic/scalar_constraint.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package symbolic
22

33
import (
4+
"fmt"
5+
46
"github.com/MatProGo-dev/SymbolicMath.go/smErrors"
57
"gonum.org/v1/gonum/mat"
68
)
@@ -281,3 +283,165 @@ func (sc ScalarConstraint) String() string {
281283
// Create the string representation
282284
return sc.LeftHandSide.String() + " " + sc.Sense.String() + " " + sc.RightHandSide.String()
283285
}
286+
287+
/*
288+
Simplify
289+
Description:
290+
291+
Simplifies the constraint by moving all variables to the left hand side and the constants to the right.
292+
*/
293+
func (sc ScalarConstraint) AsSimplifiedConstraint() Constraint {
294+
return sc.Simplify()
295+
}
296+
297+
func (sc ScalarConstraint) Variables() []Variable {
298+
return VariablesInThisConstraint(sc)
299+
}
300+
301+
func (sc ScalarConstraint) ScaleBy(factor float64) Constraint {
302+
// Check that the constraint is well formed.
303+
err := sc.Check()
304+
if err != nil {
305+
panic(err)
306+
}
307+
308+
// Scale the left hand side
309+
newLHS := sc.LeftHandSide.Multiply(factor).(ScalarExpression)
310+
311+
// Scale the right hand side
312+
newRHS := sc.RightHandSide.Multiply(factor).(ScalarExpression)
313+
314+
// If the factor is negative, then flip the sense of the constraint
315+
newSense := sc.Sense
316+
if factor < 0 {
317+
if sc.Sense == SenseLessThanEqual {
318+
newSense = SenseGreaterThanEqual
319+
} else if sc.Sense == SenseGreaterThanEqual {
320+
newSense = SenseLessThanEqual
321+
}
322+
}
323+
324+
// Return the new constraint
325+
return ScalarConstraint{
326+
LeftHandSide: newLHS,
327+
RightHandSide: newRHS,
328+
Sense: newSense,
329+
}
330+
}
331+
332+
/*
333+
ImpliesThisIsAlsoSatisfied
334+
Description:
335+
336+
Returns true if this constraint implies that the other constraint is also satisfied.
337+
*/
338+
func (sc ScalarConstraint) ImpliesThisIsAlsoSatisfied(other Constraint) bool {
339+
// Check that the constraint is well formed.
340+
err := sc.Check()
341+
if err != nil {
342+
panic(err)
343+
}
344+
345+
// Check that the other constraint is well formed.
346+
err = other.Check()
347+
if err != nil {
348+
panic(err)
349+
}
350+
351+
// Simplify both constraints
352+
sc = sc.Simplify()
353+
354+
switch otherC := other.(type) {
355+
case ScalarConstraint:
356+
otherC = otherC.Simplify()
357+
358+
// Naive implication check:
359+
// 1. Both constraints contain only 1 variable AND it is the same variable. Then, simply check the bounds.
360+
containsOneVar := len(sc.Variables()) == 1 && len(otherC.Variables()) == 1
361+
scAndOtherShareSameVar := len(UnionOfVariables(sc.Variables(), otherC.Variables())) == 1
362+
363+
if containsOneVar && scAndOtherShareSameVar {
364+
// Get the coefficient of the single variable
365+
scCoeffVector := sc.LeftHandSide.LinearCoeff(sc.Variables())
366+
scCoeff := scCoeffVector.AtVec(0)
367+
otherCCoeffVector := otherC.LeftHandSide.LinearCoeff(otherC.Variables())
368+
otherCCoeff := otherCCoeffVector.AtVec(0)
369+
370+
// If the coefficient of scCoeff is < 0,
371+
// then flip the signs of both sides of the constraint
372+
if scCoeff < 0 {
373+
sc = sc.ScaleBy(-1).(ScalarConstraint)
374+
}
375+
376+
if otherCCoeff < 0 {
377+
otherC = otherC.ScaleBy(-1).(ScalarConstraint)
378+
}
379+
380+
// The implication holds if all of the following are true:
381+
// 1. The sense of sc and otherC are either the same (or one is equality)
382+
// 2. The bounds of the constraint with the LessThanEqual or GreaterThanEqual sense are within the bounds of the other constraint.
383+
sensesAreCompatible := sc.Sense == otherC.Sense ||
384+
sc.Sense == SenseEqual ||
385+
otherC.Sense == SenseEqual
386+
387+
if !sensesAreCompatible {
388+
return false
389+
}
390+
391+
switch sc.Sense {
392+
case SenseLessThanEqual:
393+
// Check the senses of otherC
394+
switch otherC.Sense {
395+
case SenseLessThanEqual:
396+
// Both are <=
397+
// Then the implication holds if the upper bound of sc is <= the upper bound of otherC
398+
return sc.RightHandSide.Constant() <= otherC.RightHandSide.Constant()
399+
default:
400+
// sc is <= and otherC is either >= or ==
401+
// Then the implication holds if the upper bound of sc is <= the lower bound of otherC
402+
return false
403+
}
404+
case SenseGreaterThanEqual:
405+
// Check the senses of otherC
406+
switch otherC.Sense {
407+
case SenseGreaterThanEqual:
408+
// Both are >=
409+
// Then the implication holds if the lower bound of sc is >= the lower bound of otherC
410+
return sc.RightHandSide.Constant() >= otherC.RightHandSide.Constant()
411+
default:
412+
// sc is >= and otherC is either <= or ==
413+
// Then the implication holds if the lower bound of sc is >= the upper bound of otherC
414+
return false
415+
}
416+
case SenseEqual:
417+
// Check the senses of otherC
418+
switch otherC.Sense {
419+
case SenseEqual:
420+
// Both are ==
421+
// Then the implication holds if the bounds are equal
422+
return sc.RightHandSide.Constant() == otherC.RightHandSide.Constant()
423+
case SenseLessThanEqual:
424+
// sc is == and otherC is <=
425+
// Then the implication holds if the bound of sc is <= the upper bound of otherC
426+
return sc.RightHandSide.Constant() <= otherC.RightHandSide.Constant()
427+
case SenseGreaterThanEqual:
428+
// sc is == and otherC is >=
429+
// Then the implication holds if the bound of sc is >= the lower bound of otherC
430+
return sc.RightHandSide.Constant() >= otherC.RightHandSide.Constant()
431+
}
432+
default:
433+
panic("unreachable code reached in ScalarConstraint.ImpliesThisIsAlsoSatisfied")
434+
}
435+
}
436+
case VectorConstraint, MatrixConstraint:
437+
// TODO: Implement more advanced implication checks.
438+
return false
439+
default:
440+
// Other types of constraints are not currently supported.
441+
panic(
442+
fmt.Errorf("implication checking between ScalarConstraint and %T is not currently supported", other),
443+
)
444+
}
445+
446+
return false
447+
}

symbolic/variable.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ func (v Variable) Plus(rightIn interface{}) Expression {
8989
switch right := rightIn.(type) {
9090
case float64:
9191
return v.Plus(K(right))
92+
case int:
93+
return v.Plus(K(float64(right)))
9294
case K:
9395
return Polynomial{
9496
Monomials: []Monomial{
@@ -316,6 +318,8 @@ func (v Variable) Multiply(rightIn interface{}) Expression {
316318
switch right := rightIn.(type) {
317319
case float64:
318320
return v.Multiply(K(right))
321+
case int:
322+
return v.Multiply(K(float64(right)))
319323
case K:
320324
// Create a new monomial
321325
monomialOut := Monomial{
@@ -650,3 +654,11 @@ func (v Variable) At(ii, jj int) ScalarExpression {
650654
// Algorithm
651655
return v
652656
}
657+
658+
func UnionOfVariables(varSlices ...[]Variable) []Variable {
659+
var allVars []Variable
660+
for _, varSlice := range varSlices {
661+
allVars = append(allVars, varSlice...)
662+
}
663+
return UniqueVars(allVars)
664+
}

0 commit comments

Comments
 (0)