Skip to content

Commit dece87d

Browse files
Merge pull request #7 from MatProGo-dev/kr-feature-problem1
Introducing a Few Important Upgrades for the Optimization Problem
2 parents 8f5ea00 + 7049718 commit dece87d

File tree

7 files changed

+496
-17
lines changed

7 files changed

+496
-17
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ go 1.21
44

55
require gonum.org/v1/gonum v0.14.0
66

7-
require github.com/MatProGo-dev/SymbolicMath.go v0.1.4
7+
require github.com/MatProGo-dev/SymbolicMath.go v0.1.8

go.sum

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,5 @@
1-
github.com/MatProGo-dev/SymbolicMath.go v0.0.0-20240104201035-f9a42f642121 h1:nnJVXcnTvAdkfR4kZRBw4Ot+KoL8S3PijlLTmKOooco=
2-
github.com/MatProGo-dev/SymbolicMath.go v0.0.0-20240104201035-f9a42f642121/go.mod h1:gKbGR/6sYWi2koMUEDIPWBPi6jQPELKle0ijIM+eaHU=
3-
github.com/MatProGo-dev/SymbolicMath.go v0.1.0 h1:FUwLQZzZhtgGj6WKyuwQE74P9UYhgbNr5eD5f8zzuu8=
4-
github.com/MatProGo-dev/SymbolicMath.go v0.1.0/go.mod h1:gKbGR/6sYWi2koMUEDIPWBPi6jQPELKle0ijIM+eaHU=
5-
github.com/MatProGo-dev/SymbolicMath.go v0.1.1 h1:YigpL7w5D8qLu0xzDAayMXePBh9LK0/w3ykvuoVZwXQ=
6-
github.com/MatProGo-dev/SymbolicMath.go v0.1.1/go.mod h1:gKbGR/6sYWi2koMUEDIPWBPi6jQPELKle0ijIM+eaHU=
7-
github.com/MatProGo-dev/SymbolicMath.go v0.1.2 h1:9tYbiHWm0doXOSipd02pxtENqBU8WhmHTrDXetPW+ms=
8-
github.com/MatProGo-dev/SymbolicMath.go v0.1.2/go.mod h1:gKbGR/6sYWi2koMUEDIPWBPi6jQPELKle0ijIM+eaHU=
9-
github.com/MatProGo-dev/SymbolicMath.go v0.1.3 h1:IeofFqvZ/jAO6LywlZR/UMl5t/KOyMAvvctVgTzvgHI=
10-
github.com/MatProGo-dev/SymbolicMath.go v0.1.3/go.mod h1:gKbGR/6sYWi2koMUEDIPWBPi6jQPELKle0ijIM+eaHU=
11-
github.com/MatProGo-dev/SymbolicMath.go v0.1.4 h1:6DaDRYoANmKMkZL0uSUSx7Ibx8Hc9S6AX+7L0hIy1A4=
12-
github.com/MatProGo-dev/SymbolicMath.go v0.1.4/go.mod h1:gKbGR/6sYWi2koMUEDIPWBPi6jQPELKle0ijIM+eaHU=
1+
github.com/MatProGo-dev/SymbolicMath.go v0.1.8 h1:lpe+6cK/2fg29WwxOykm4hKvfJeqvFUBGturC9qh5ug=
2+
github.com/MatProGo-dev/SymbolicMath.go v0.1.8/go.mod h1:gKbGR/6sYWi2koMUEDIPWBPi6jQPELKle0ijIM+eaHU=
133
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
144
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
155
gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0=

mpiErrors/no_objective_defined.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package mpiErrors
2+
3+
type NoObjectiveDefinedError struct{}
4+
5+
func (e NoObjectiveDefinedError) Error() string {
6+
return "No objective defined for the optimization problem; please define one with SetObjective()"
7+
}

problem/objective.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,13 @@ type Objective struct {
1414
func NewObjective(e symbolic.Expression, sense ObjSense) *Objective {
1515
return &Objective{e, sense}
1616
}
17+
18+
/*
19+
IsLinear
20+
Description:
21+
22+
This method returns true if the objective is linear, false otherwise.
23+
*/
24+
func (o *Objective) IsLinear() bool {
25+
return symbolic.IsLinear(o.Expression)
26+
}

problem/optimization_problem.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package problem
22

33
import (
44
"fmt"
5+
6+
"github.com/MatProGo-dev/MatProInterface.go/mpiErrors"
57
"github.com/MatProGo-dev/MatProInterface.go/optim"
68
"github.com/MatProGo-dev/SymbolicMath.go/symbolic"
79
)
@@ -268,3 +270,73 @@ func From(inputModel optim.Model) (*OptimizationProblem, error) {
268270
return newOptimProblem, nil
269271

270272
}
273+
274+
/*
275+
Check
276+
Description:
277+
278+
Checks that the OptimizationProblem is valid.
279+
*/
280+
func (op *OptimizationProblem) Check() error {
281+
// Check Objective
282+
if op.Objective == (Objective{}) {
283+
return mpiErrors.NoObjectiveDefinedError{}
284+
}
285+
286+
err := op.Objective.Check()
287+
if err != nil {
288+
return fmt.Errorf("the objective is not valid: %v", err)
289+
}
290+
291+
// Check Variables
292+
for _, variable := range op.Variables {
293+
err = variable.Check()
294+
if err != nil {
295+
return fmt.Errorf("the variable is not valid: %v", err)
296+
}
297+
}
298+
299+
// Check Constraints
300+
for _, constraint := range op.Constraints {
301+
err = constraint.Check()
302+
if err != nil {
303+
return fmt.Errorf("the constraint is not valid: %v", err)
304+
}
305+
}
306+
307+
// All Checks Passed!
308+
return nil
309+
}
310+
311+
/*
312+
IsLinear
313+
Description:
314+
315+
Checks if the optimization problem is linear.
316+
Per the definition of a linear optimization problem, the problem is linear if and only if:
317+
1. The objective function is linear (i.e., a constant or an affine combination of variables).
318+
2. All constraints are linear (i.e., an affine combination of variables in an inequality or equality).
319+
*/
320+
func (op *OptimizationProblem) IsLinear() bool {
321+
// Input Processing
322+
// Verify that the problem is well-formed
323+
err := op.Check()
324+
if err != nil {
325+
panic(fmt.Errorf("the optimization problem is not well-formed: %v", err))
326+
}
327+
328+
// Check Objective
329+
if !op.Objective.IsLinear() {
330+
return false
331+
}
332+
333+
// Check Constraints
334+
for _, constraint := range op.Constraints {
335+
if !constraint.IsLinear() {
336+
return false
337+
}
338+
}
339+
340+
// All Checks Passed!
341+
return true
342+
}

testing/optim/scalar_constraint_test.go

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ Description:
77
*/
88

99
import (
10+
"testing"
11+
1012
"github.com/MatProGo-dev/MatProInterface.go/optim"
1113
"gonum.org/v1/gonum/mat"
12-
"testing"
1314
)
1415

1516
func TestScalarConstraint_ScalarConstraint1(t *testing.T) {
@@ -64,7 +65,7 @@ func TestScalarConstraint_IsLinear1(t *testing.T) {
6465
}
6566

6667
/*
67-
TestScalarConstraint_IsLinear1
68+
TestScalarConstraint_IsLinear2
6869
Description:
6970
7071
Detects whether a simple inequality between
@@ -99,6 +100,43 @@ func TestScalarConstraint_IsLinear2(t *testing.T) {
99100

100101
}
101102

103+
/*
104+
TestScalarConstraint_IsLinear3
105+
Description:
106+
107+
Detects whether a simple inequality between
108+
a variable and a constant is a quadratic expression.
109+
The function should identify that this is NOT a linear expression.
110+
*/
111+
func TestScalarConstraint_IsLinear3(t *testing.T) {
112+
// Constants
113+
m := optim.NewModel("scalar-constraint-test1")
114+
v1 := m.AddVariable()
115+
sqe2 := optim.ScalarQuadraticExpression{
116+
L: optim.OnesVector(1),
117+
Q: *mat.NewDense(1, 1, []float64{3.14}),
118+
X: optim.VarVector{Elements: []optim.Variable{v1}},
119+
}
120+
121+
k1 := optim.K(2.8)
122+
123+
// Algorithm
124+
sc1 := optim.ScalarConstraint{
125+
LeftHandSide: k1,
126+
RightHandSide: sqe2,
127+
Sense: optim.SenseEqual,
128+
}
129+
130+
tf, err := sc1.IsLinear()
131+
if err != nil {
132+
t.Errorf("unexpected error: %v", err)
133+
}
134+
if tf {
135+
t.Errorf("sc1 is not linear, but function claims it is!")
136+
}
137+
138+
}
139+
102140
/*
103141
TestScalarConstraint_Simplify1
104142
Description:
@@ -138,3 +176,97 @@ func TestScalarConstraint_Simplify1(t *testing.T) {
138176
t.Errorf("Remainder on LHS was not contained properly")
139177
}
140178
}
179+
180+
/*
181+
TestScalarConstraint_Simplify2
182+
Description:
183+
184+
Attempts to simplify the constraint between
185+
a scalar linear epression and a variable.
186+
The resulting constraint should have a zero remainder on the right hand side
187+
and the left hand side should contain a variable vector with 1 more element than it started with.
188+
*/
189+
func TestScalarConstraint_Simplify2(t *testing.T) {
190+
// Constants
191+
m := optim.NewModel("scalar-constraint-test2")
192+
vv1 := m.AddVariableVector(3)
193+
sle2 := optim.ScalarLinearExpr{
194+
L: optim.OnesVector(vv1.Len()),
195+
X: vv1,
196+
C: 2.0,
197+
}
198+
v3 := m.AddVariable()
199+
200+
// Create sles
201+
sc1 := optim.ScalarConstraint{
202+
LeftHandSide: sle2,
203+
RightHandSide: v3,
204+
Sense: optim.SenseEqual,
205+
}
206+
207+
// Attempt to simplify
208+
sc2, err := sc1.Simplify()
209+
if err != nil {
210+
t.Errorf("unexpected error during simplify(): %v", err)
211+
}
212+
213+
if float64(sc2.RightHandSide.(optim.K)) != 0.0 {
214+
t.Errorf("Remainder on LHS was not contained properly")
215+
}
216+
217+
if sc2.LeftHandSide.(optim.ScalarLinearExpr).X.Len() != 4 {
218+
t.Errorf("Variable vector did not increase in size")
219+
}
220+
}
221+
222+
/*
223+
TestScalarConstraint_Simplify3
224+
Description:
225+
226+
Attempts to simplify the constraint between
227+
a scalar linear epression and a scalar quadratic expression.
228+
*/
229+
func TestScalarConstraint_Simplify3(t *testing.T) {
230+
// Constants
231+
m := optim.NewModel("scalar-constraint-test3")
232+
vv1 := m.AddVariableVector(3)
233+
sle2 := optim.ScalarLinearExpr{
234+
L: optim.OnesVector(vv1.Len()),
235+
X: vv1,
236+
C: 2.0,
237+
}
238+
sqe3 := optim.ScalarQuadraticExpression{
239+
L: optim.OnesVector(3),
240+
Q: *mat.NewDense(
241+
3, 3,
242+
[]float64{
243+
3.14, 0.0, 0.0,
244+
0.0, 3.14, 0.0,
245+
0.0, 0.0, 3.14,
246+
},
247+
),
248+
X: vv1,
249+
C: 1.5,
250+
}
251+
252+
// Create sles
253+
sc1 := optim.ScalarConstraint{
254+
LeftHandSide: sle2,
255+
RightHandSide: sqe3,
256+
Sense: optim.SenseLessThanEqual,
257+
}
258+
259+
// Attempt to simplify
260+
sc2, err := sc1.Simplify()
261+
if err != nil {
262+
t.Errorf("unexpected error during simplify(): %v", err)
263+
}
264+
265+
if float64(sc2.RightHandSide.(optim.K)) != sqe3.C {
266+
t.Errorf(
267+
"Remainder on RHS was not contained properly; expected %v, received %v",
268+
sqe3.C,
269+
sc2.RightHandSide.(optim.K),
270+
)
271+
}
272+
}

0 commit comments

Comments
 (0)