Skip to content

Commit 3fdab38

Browse files
Merge pull request #5 from MatProGo-dev/kr-example-qp1
Introduced a Simplify() and IsLinear() method to ScalarConstraint to make some of Gurobi.go's efforts easier
2 parents b90c05f + 7aaf198 commit 3fdab38

File tree

3 files changed

+199
-22
lines changed

3 files changed

+199
-22
lines changed

README.md

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -133,33 +133,15 @@ code. Hopefully, this is avoided using this format.
133133
* [X] Create New AddConstr methods which work for vector constraints
134134
* [ ] Mult
135135
* [ ] General Function (in operators.go)
136-
* [ ] Methods for
137-
* [X] Scalars
138-
* [X] Constant
139-
* [X] Var
140-
* [X] ScalarLinearExpression
141-
* [X] QuadraticExpression
142-
* [ ] Vectors
143-
* [ ] Vector Constant
144-
* [ ] VarVector
145-
* [ ] VectorLinearExpression
146136
* [ ] Plus
147137
* [ ] General Function (in operators.go)
148138
* [ ] Introducing Optional Input for Variable Name to Var/VarVector
149139
* [ ] Consider renaming VarVector to VectorVar
150140
* [ ] Decide whether or not we really need the Coeffs() method (What is it doing?)
151-
* [ ] Create function for easily creating MatDense:
152-
* [ ] ones matrices
153-
* [ ] Create function for:
154-
* [ ] IsScalar()
155-
* [ ] IsVector()
156-
* [X] VectorConstraint
157-
* [X] AtVec()
158141
* [ ] Write changes to all AtVec() methods to output both elements AND errors (so we can detect out of length calls)
159142
* [ ] Determine whether or not to keep the Solution and Solver() interfaces in this module. It seems like they can be solver-specific.
160143
* [ ] Introduce MatrixVar object
161-
* [ ] Add The Following to the Expression Interface
162-
* [ ] Comparison
163-
* [ ] LessEq
164-
* [ ] GreaterEq
165-
* [ ] Eq
144+
* [ ] Add Check() to:
145+
* [ ] Expression
146+
* [ ] ScalarExpression
147+
* [ ] VectorExpression interfaces

optim/scalar_constraint.go

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

3+
import "fmt"
4+
35
// ScalarConstraint represnts a linear constraint of the form x <= y, x >= y, or
46
// x == y. ScalarConstraint uses a left and right hand side expressions along with a
57
// constraint sense (<=, >=, ==) to represent a generalized linear constraint
@@ -17,6 +19,93 @@ func (sc ScalarConstraint) Right() Expression {
1719
return sc.RightHandSide
1820
}
1921

22+
/*
23+
IsLinear
24+
Description:
25+
26+
Describes whether or not a given linear constraint is
27+
linear or not.
28+
*/
29+
func (sc ScalarConstraint) IsLinear() (bool, error) {
30+
// Check left and right side.
31+
if _, tf := sc.LeftHandSide.(ScalarQuadraticExpression); tf {
32+
return false, nil
33+
}
34+
35+
// If left side has degree less than two, then this only depends
36+
// on the right side.
37+
if _, tf := sc.RightHandSide.(ScalarQuadraticExpression); tf {
38+
return false, nil
39+
}
40+
41+
// Otherwise return true
42+
return true, nil
43+
}
44+
45+
/*
46+
Simplify
47+
Description:
48+
49+
Moves all of the variables of the ScalarConstraint to its
50+
left hand side.
51+
*/
52+
func (sc ScalarConstraint) Simplify() (ScalarConstraint, error) {
53+
// Create LHS
54+
newLHS := sc.LeftHandSide
55+
56+
// Algorithm
57+
switch right := sc.RightHandSide.(type) {
58+
case K:
59+
return sc, nil
60+
case Variable:
61+
newLHS, err := newLHS.Plus(right.Multiply(-1.0))
62+
if err != nil {
63+
return sc, err
64+
}
65+
newLHSAsSE, _ := ToScalarExpression(newLHS)
66+
67+
return ScalarConstraint{
68+
LeftHandSide: newLHSAsSE,
69+
RightHandSide: K(0),
70+
Sense: sc.Sense,
71+
}, nil
72+
case ScalarLinearExpr:
73+
rightWithoutConstant := right
74+
rightWithoutConstant.C = 0.0
75+
76+
newLHS, err := newLHS.Plus(rightWithoutConstant.Multiply(-1.0))
77+
if err != nil {
78+
return sc, err
79+
}
80+
newLHSAsSE, _ := ToScalarExpression(newLHS)
81+
82+
return ScalarConstraint{
83+
LeftHandSide: newLHSAsSE,
84+
RightHandSide: K(right.C),
85+
Sense: sc.Sense,
86+
}, nil
87+
case ScalarQuadraticExpression:
88+
rightWithoutConstant := right
89+
rightWithoutConstant.C = 0.0
90+
91+
newLHS, err := newLHS.Plus(rightWithoutConstant.Multiply(-1.0))
92+
if err != nil {
93+
return sc, err
94+
}
95+
newLHSAsSE, _ := ToScalarExpression(newLHS)
96+
97+
return ScalarConstraint{
98+
LeftHandSide: newLHSAsSE,
99+
RightHandSide: K(right.C),
100+
Sense: sc.Sense,
101+
}, nil
102+
103+
default:
104+
return sc, fmt.Errorf("unexpected type of right hand side: %T", right)
105+
}
106+
107+
}
108+
20109
// ConstrSense represents if the constraint x <= y, x >= y, or x == y. For easy
21110
// integration with Gurobi, the senses have been encoding using a byte in
22111
// the same way Gurobi encodes the constraint senses.

testing/optim/scalar_constraint_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Description:
88

99
import (
1010
"github.com/MatProGo-dev/MatProInterface.go/optim"
11+
"gonum.org/v1/gonum/mat"
1112
"testing"
1213
)
1314

@@ -30,5 +31,110 @@ func TestScalarConstraint_ScalarConstraint1(t *testing.T) {
3031
sc1.Sense,
3132
)
3233
}
34+
}
35+
36+
/*
37+
TestScalarConstraint_IsLinear1
38+
Description:
39+
40+
Detects whether a simple inequality between
41+
a variable and a constant is a linear constraint.
42+
*/
43+
func TestScalarConstraint_IsLinear1(t *testing.T) {
44+
// Constants
45+
m := optim.NewModel("scalar-constraint-test1")
46+
v1 := m.AddVariable()
47+
k1 := optim.K(2.8)
48+
49+
// Algorithm
50+
sc1 := optim.ScalarConstraint{
51+
LeftHandSide: v1,
52+
RightHandSide: k1,
53+
Sense: optim.SenseEqual,
54+
}
55+
56+
tf, err := sc1.IsLinear()
57+
if err != nil {
58+
t.Errorf("unexpected error: %v", err)
59+
}
60+
if !tf {
61+
t.Errorf("sc1 is linear, but function claims it is not!")
62+
}
63+
64+
}
65+
66+
/*
67+
TestScalarConstraint_IsLinear1
68+
Description:
69+
70+
Detects whether a simple inequality between
71+
a variable and a constant is a linear constraint.
72+
*/
73+
func TestScalarConstraint_IsLinear2(t *testing.T) {
74+
// Constants
75+
m := optim.NewModel("scalar-constraint-test1")
76+
v1 := m.AddVariable()
77+
sqe2 := optim.ScalarQuadraticExpression{
78+
L: optim.OnesVector(1),
79+
Q: *mat.NewDense(1, 1, []float64{3.14}),
80+
X: optim.VarVector{Elements: []optim.Variable{v1}},
81+
}
82+
83+
k1 := optim.K(2.8)
84+
85+
// Algorithm
86+
sc1 := optim.ScalarConstraint{
87+
LeftHandSide: sqe2,
88+
RightHandSide: k1,
89+
Sense: optim.SenseEqual,
90+
}
91+
92+
tf, err := sc1.IsLinear()
93+
if err != nil {
94+
t.Errorf("unexpected error: %v", err)
95+
}
96+
if tf {
97+
t.Errorf("sc1 is not linear, but function claims it is!")
98+
}
99+
100+
}
101+
102+
/*
103+
TestScalarConstraint_Simplify1
104+
Description:
105+
106+
Attempts to simplify the constraint between
107+
a scalar linear epression and a scalar linear expression.
108+
*/
109+
func TestScalarConstraint_Simplify1(t *testing.T) {
110+
// Constants
111+
m := optim.NewModel("scalar-constraint-test1")
112+
vv1 := m.AddVariableVector(3)
113+
sle2 := optim.ScalarLinearExpr{
114+
L: optim.OnesVector(vv1.Len()),
115+
X: vv1,
116+
C: 2.0,
117+
}
118+
sle3 := optim.ScalarLinearExpr{
119+
L: *mat.NewVecDense(vv1.Len(), []float64{1.0, 2.0, 3.0}),
120+
X: vv1,
121+
C: 1.0,
122+
}
33123

124+
// Create sles
125+
sc1 := optim.ScalarConstraint{
126+
LeftHandSide: sle2,
127+
RightHandSide: sle3,
128+
Sense: optim.SenseEqual,
129+
}
130+
131+
// Attempt to simplify
132+
sc2, err := sc1.Simplify()
133+
if err != nil {
134+
t.Errorf("unexpected error during simplify(): %v", err)
135+
}
136+
137+
if float64(sc2.RightHandSide.(optim.K)) != 1.0 {
138+
t.Errorf("Remainder on LHS was not contained properly")
139+
}
34140
}

0 commit comments

Comments
 (0)