Skip to content
47 changes: 47 additions & 0 deletions symbolic/constraint.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ type Constraint interface {
IsLinear() bool
Substitute(vIn Variable, seIn ScalarExpression) Constraint
SubstituteAccordingTo(subMap map[Variable]Expression) Constraint

// Variables
// Returns a slice of all the variables in the constraint.
Variables() []Variable

// ImpliesThisIsAlsoSatisfied
// Returns true if this constraint implies that the other constraint is also satisfied.
ImpliesThisIsAlsoSatisfied(other Constraint) bool

// AsSimplifiedConstraint
// Simplifies the constraint by moving all variables to the left hand side and the constants to the right.
AsSimplifiedConstraint() Constraint
}

func IsConstraint(c interface{}) bool {
Expand All @@ -36,3 +48,38 @@ func IsConstraint(c interface{}) bool {
// Return false, if the constraint is not a scalar or vector constraint.
return false
}

/*
Variables
Description:

Returns a slice of all the variables in the constraint.
*/
func VariablesInThisConstraint(c Constraint) []Variable {
// Setup
varsMap := make(map[Variable]bool)

// Input check
err := c.Check()
if err != nil {
panic(err)
}

// Get variables from the left hand side
for _, v := range c.Left().Variables() {
varsMap[v] = true
}

// Get variables from the right hand side
for _, v := range c.Right().Variables() {
varsMap[v] = true
}

// Convert the map to a slice
vars := make([]Variable, 0, len(varsMap))
for v := range varsMap {
vars = append(vars, v)
}

return vars
}
81 changes: 81 additions & 0 deletions symbolic/matrix_constraint.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,84 @@ func (mc MatrixConstraint) SubstituteAccordingTo(subMap map[Variable]Expression)

return MatrixConstraint{newLHS, newRHS, mc.Sense}
}

/*
AsSimplifiedConstraint
Description:

Simplifies the constraint by moving all variables to the left hand side and the constants to the right.
*/
func (mc MatrixConstraint) AsSimplifiedConstraint() Constraint {
// Create Left Hand side of all of the expressions
var newLHS Expression = mc.LeftHandSide.Minus(mc.LeftHandSide.Constant())
newLHS = newLHS.Minus(
mc.RightHandSide.Minus(mc.RightHandSide.Constant()),
)

// Create Right Hand Side of only constants
var newRHS Expression = DenseToKMatrix(mc.RightHandSide.Constant()).Minus(
mc.LeftHandSide.Constant(),
)

// Return new constraint
return MatrixConstraint{
LeftHandSide: newLHS.(MatrixExpression),
RightHandSide: newRHS.(MatrixExpression),
Sense: mc.Sense,
}
}

/*
Variables
Description:

Returns a slice of all the variables in the constraint.
*/
func (mc MatrixConstraint) Variables() []Variable {
return VariablesInThisConstraint(mc)
}

/*
ImpliesThisIsAlsoSatisfied
Description:

Returns true if this constraint implies that the other constraint is also satisfied.
*/
func (mc MatrixConstraint) ImpliesThisIsAlsoSatisfied(other Constraint) bool {
// Input Processing
err := mc.Check()
if err != nil {
panic(err)
}

err = other.Check()
if err != nil {
panic(err)
}

// Implication Avenues
switch otherC := other.(type) {
case ScalarConstraint:
// If the other constraint is a scalar constraint,
// then it can only be implied if:
// - one of the elements of the matrix constraint implies the scalar constraint.
for i := 0; i < mc.Dims()[0]; i++ {
for j := 0; j < mc.Dims()[1]; j++ {
if mc.At(i, j).ImpliesThisIsAlsoSatisfied(otherC) {
return true
}
}
}
case VectorConstraint, MatrixConstraint:
// TODO: Implement more advanced implication checks.
return false
default:
// Other types of constraints are not currently supported.
panic(
fmt.Errorf("implication checking between MatrixConstraint and %T is not currently supported", other),
)
}

// If no avenues for implication were found, return false.
return false
}
164 changes: 164 additions & 0 deletions symbolic/scalar_constraint.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package symbolic

import (
"fmt"

"github.com/MatProGo-dev/SymbolicMath.go/smErrors"
"gonum.org/v1/gonum/mat"
)
Expand Down Expand Up @@ -281,3 +283,165 @@ func (sc ScalarConstraint) String() string {
// Create the string representation
return sc.LeftHandSide.String() + " " + sc.Sense.String() + " " + sc.RightHandSide.String()
}

/*
Simplify
Description:

Simplifies the constraint by moving all variables to the left hand side and the constants to the right.
*/
func (sc ScalarConstraint) AsSimplifiedConstraint() Constraint {
return sc.Simplify()
}

func (sc ScalarConstraint) Variables() []Variable {
return VariablesInThisConstraint(sc)
}

func (sc ScalarConstraint) ScaleBy(factor float64) Constraint {
// Check that the constraint is well formed.
err := sc.Check()
if err != nil {
panic(err)
}

// Scale the left hand side
newLHS := sc.LeftHandSide.Multiply(factor).(ScalarExpression)

// Scale the right hand side
newRHS := sc.RightHandSide.Multiply(factor).(ScalarExpression)

// If the factor is negative, then flip the sense of the constraint
newSense := sc.Sense
if factor < 0 {
if sc.Sense == SenseLessThanEqual {
newSense = SenseGreaterThanEqual
} else if sc.Sense == SenseGreaterThanEqual {
newSense = SenseLessThanEqual
}
}

// Return the new constraint
return ScalarConstraint{
LeftHandSide: newLHS,
RightHandSide: newRHS,
Sense: newSense,
}
}

/*
ImpliesThisIsAlsoSatisfied
Description:

Returns true if this constraint implies that the other constraint is also satisfied.
*/
func (sc ScalarConstraint) ImpliesThisIsAlsoSatisfied(other Constraint) bool {
// Check that the constraint is well formed.
err := sc.Check()
if err != nil {
panic(err)
}

// Check that the other constraint is well formed.
err = other.Check()
if err != nil {
panic(err)
}

// Simplify both constraints
sc = sc.Simplify()

switch otherC := other.(type) {
case ScalarConstraint:
otherC = otherC.Simplify()

// Naive implication check:
// 1. Both constraints contain only 1 variable AND it is the same variable. Then, simply check the bounds.
containsOneVar := len(sc.Variables()) == 1 && len(otherC.Variables()) == 1
scAndOtherShareSameVar := len(UnionOfVariables(sc.Variables(), otherC.Variables())) == 1

if containsOneVar && scAndOtherShareSameVar {
// Get the coefficient of the single variable
scCoeffVector := sc.LeftHandSide.LinearCoeff(sc.Variables())
scCoeff := scCoeffVector.AtVec(0)
otherCCoeffVector := otherC.LeftHandSide.LinearCoeff(otherC.Variables())
otherCCoeff := otherCCoeffVector.AtVec(0)

// If the coefficient of scCoeff is < 0,
// then flip the signs of both sides of the constraint
if scCoeff < 0 {
sc = sc.ScaleBy(-1).(ScalarConstraint)
}

if otherCCoeff < 0 {
otherC = otherC.ScaleBy(-1).(ScalarConstraint)
}

// The implication holds if all of the following are true:
// 1. The sense of sc and otherC are either the same (or one is equality)
// 2. The bounds of the constraint with the LessThanEqual or GreaterThanEqual sense are within the bounds of the other constraint.
sensesAreCompatible := sc.Sense == otherC.Sense ||
sc.Sense == SenseEqual ||
otherC.Sense == SenseEqual

if !sensesAreCompatible {
return false
}

switch sc.Sense {
case SenseLessThanEqual:
// Check the senses of otherC
switch otherC.Sense {
case SenseLessThanEqual:
// Both are <=
// Then the implication holds if the upper bound of sc is <= the upper bound of otherC
return sc.RightHandSide.Constant() <= otherC.RightHandSide.Constant()
default:
// sc is <= and otherC is either >= or ==
// Then the implication holds if the upper bound of sc is <= the lower bound of otherC
return false
}
case SenseGreaterThanEqual:
// Check the senses of otherC
switch otherC.Sense {
case SenseGreaterThanEqual:
// Both are >=
// Then the implication holds if the lower bound of sc is >= the lower bound of otherC
return sc.RightHandSide.Constant() >= otherC.RightHandSide.Constant()
default:
// sc is >= and otherC is either <= or ==
// Then the implication holds if the lower bound of sc is >= the upper bound of otherC
return false
}
case SenseEqual:
// Check the senses of otherC
switch otherC.Sense {
case SenseEqual:
// Both are ==
// Then the implication holds if the bounds are equal
return sc.RightHandSide.Constant() == otherC.RightHandSide.Constant()
case SenseLessThanEqual:
// sc is == and otherC is <=
// Then the implication holds if the bound of sc is <= the upper bound of otherC
return sc.RightHandSide.Constant() <= otherC.RightHandSide.Constant()
case SenseGreaterThanEqual:
// sc is == and otherC is >=
// Then the implication holds if the bound of sc is >= the lower bound of otherC
return sc.RightHandSide.Constant() >= otherC.RightHandSide.Constant()
}
default:
panic("unreachable code reached in ScalarConstraint.ImpliesThisIsAlsoSatisfied")
}
}
case VectorConstraint, MatrixConstraint:
// TODO: Implement more advanced implication checks.
return false
default:
// Other types of constraints are not currently supported.
panic(
fmt.Errorf("implication checking between ScalarConstraint and %T is not currently supported", other),
)
}

return false
}
12 changes: 12 additions & 0 deletions symbolic/variable.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ func (v Variable) Plus(rightIn interface{}) Expression {
switch right := rightIn.(type) {
case float64:
return v.Plus(K(right))
case int:
return v.Plus(K(float64(right)))
case K:
return Polynomial{
Monomials: []Monomial{
Expand Down Expand Up @@ -316,6 +318,8 @@ func (v Variable) Multiply(rightIn interface{}) Expression {
switch right := rightIn.(type) {
case float64:
return v.Multiply(K(right))
case int:
return v.Multiply(K(float64(right)))
case K:
// Create a new monomial
monomialOut := Monomial{
Expand Down Expand Up @@ -650,3 +654,11 @@ func (v Variable) At(ii, jj int) ScalarExpression {
// Algorithm
return v
}

func UnionOfVariables(varSlices ...[]Variable) []Variable {
var allVars []Variable
for _, varSlice := range varSlices {
allVars = append(allVars, varSlice...)
}
return UniqueVars(allVars)
}
Loading
Loading