Skip to content

Commit da2d52c

Browse files
committed
Added a new error for when an optimization problem is not linear
1 parent d008772 commit da2d52c

File tree

4 files changed

+194
-16
lines changed

4 files changed

+194
-16
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package causeOfProblemNonlinearity
2+
3+
type Cause string
4+
5+
const (
6+
Objective Cause = "Objective"
7+
Constraint = "Constraint"
8+
NotWellDefined = "NotWellDefined"
9+
)

mpiErrors/not_linear.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package mpiErrors
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/MatProGo-dev/MatProInterface.go/causeOfProblemNonlinearity"
7+
)
8+
9+
type ProblemNotLinearError struct {
10+
ProblemName string
11+
Cause causeOfProblemNonlinearity.Cause
12+
ConstraintIndex int // Index of the constraint that is not linear, if applicable
13+
}
14+
15+
func (e ProblemNotLinearError) Error() string {
16+
preamble := "The problem " + e.ProblemName + " is not linear"
17+
switch e.Cause {
18+
case causeOfProblemNonlinearity.Objective:
19+
preamble += "; the objective is not linear"
20+
case causeOfProblemNonlinearity.Constraint:
21+
preamble += fmt.Sprintf("; constraint #%v is not linear", e.ConstraintIndex)
22+
case causeOfProblemNonlinearity.NotWellDefined:
23+
preamble += "; the problem is not well defined"
24+
default:
25+
preamble += "; the cause of the problem is not recognized (create an issue on GitHub if you think this is a bug!)"
26+
}
27+
// Return the error message
28+
return preamble
29+
}

problem/optimization_problem.go

Lines changed: 63 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package problem
33
import (
44
"fmt"
55

6+
"github.com/MatProGo-dev/MatProInterface.go/causeOfProblemNonlinearity"
67
"github.com/MatProGo-dev/MatProInterface.go/mpiErrors"
78
"github.com/MatProGo-dev/MatProInterface.go/optim"
89
getKVector "github.com/MatProGo-dev/SymbolicMath.go/get/KVector"
@@ -320,25 +321,28 @@ Description:
320321
2. All constraints are linear (i.e., an affine combination of variables in an inequality or equality).
321322
*/
322323
func (op *OptimizationProblem) IsLinear() bool {
323-
// Input Processing
324-
// Verify that the problem is well-formed
325-
err := op.Check()
324+
// Run the check method
325+
err := op.CheckIfLinear()
326326
if err != nil {
327-
panic(fmt.Errorf("the optimization problem is not well-formed: %v", err))
328-
}
327+
// If the check method returns a NotWellDefinedError, then the problem is not well defined
328+
// and we should panic.
329+
notWellDefinedError := mpiErrors.ProblemNotLinearError{
330+
ProblemName: op.Name,
331+
Cause: causeOfProblemNonlinearity.NotWellDefined,
332+
ConstraintIndex: -1,
333+
}
334+
if err.Error() == notWellDefinedError.Error() {
335+
panic(
336+
fmt.Errorf(
337+
"the optimization problem is not well defined; %v",
338+
op.Check(),
339+
))
340+
}
329341

330-
// Check Objective
331-
if !op.Objective.IsLinear() {
342+
// If the check returns any other error, then the problem is not linear
332343
return false
333344
}
334345

335-
// Check Constraints
336-
for _, constraint := range op.Constraints {
337-
if !constraint.IsLinear() {
338-
return false
339-
}
340-
}
341-
342346
// All Checks Passed!
343347
return true
344348
}
@@ -645,12 +649,12 @@ func (problemIn *OptimizationProblem) ToLPStandardForm1() (*OptimizationProblem,
645649
// Input Processing
646650
err := problemIn.Check()
647651
if err != nil {
648-
panic(fmt.Errorf("the optimization problem is not well-formed: %v", err))
652+
return nil, nil, fmt.Errorf("the optimization problem is not well-formed: %v", err)
649653
}
650654

651655
// Check if the problem is linear
652656
if !problemIn.IsLinear() {
653-
panic(fmt.Errorf("the optimization problem is not linear: %v", err))
657+
return nil, nil, problemIn.CheckIfLinear()
654658
}
655659

656660
// Setup
@@ -810,3 +814,46 @@ func (problemIn *OptimizationProblem) ToLPStandardForm1() (*OptimizationProblem,
810814
// Return the new problem and the slack variables
811815
return problemInStandardForm, slackVariables, nil
812816
}
817+
818+
/*
819+
CheckIfLinear
820+
Description:
821+
822+
Checks the current optimization problem to see if it is linear.
823+
Returns an error if the problem is not linear.
824+
*/
825+
func (op *OptimizationProblem) CheckIfLinear() error {
826+
// Input Processing
827+
// Verify that the problem is well-formed
828+
err := op.Check()
829+
if err != nil {
830+
return mpiErrors.ProblemNotLinearError{
831+
ProblemName: op.Name,
832+
Cause: causeOfProblemNonlinearity.NotWellDefined,
833+
ConstraintIndex: -1,
834+
}
835+
}
836+
837+
// Check Objective
838+
if !op.Objective.IsLinear() {
839+
return mpiErrors.ProblemNotLinearError{
840+
ProblemName: op.Name,
841+
Cause: causeOfProblemNonlinearity.Objective,
842+
ConstraintIndex: -2,
843+
}
844+
}
845+
846+
// Check Constraints
847+
for ii, constraint := range op.Constraints {
848+
if !constraint.IsLinear() {
849+
return mpiErrors.ProblemNotLinearError{
850+
ProblemName: op.Name,
851+
Cause: causeOfProblemNonlinearity.Constraint,
852+
ConstraintIndex: ii,
853+
}
854+
}
855+
}
856+
857+
// All Checks Passed!
858+
return nil
859+
}

testing/problem/optimization_problem_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"strings"
1313
"testing"
1414

15+
"github.com/MatProGo-dev/MatProInterface.go/causeOfProblemNonlinearity"
1516
"github.com/MatProGo-dev/MatProInterface.go/mpiErrors"
1617
"github.com/MatProGo-dev/MatProInterface.go/optim"
1718
"github.com/MatProGo-dev/MatProInterface.go/problem"
@@ -2206,3 +2207,95 @@ func TestOptimizationProblem_ToLPStandardForm3(t *testing.T) {
22062207
}
22072208

22082209
}
2210+
2211+
/*
2212+
TestOptimizationProblem_ToLPStandardForm4
2213+
Description:
2214+
2215+
This test verifies that the ToLPStandardForm function throws an error
2216+
when called on a problem that is not linear.
2217+
In this case, we will define a problem with a quadratic objective function.
2218+
The problem will have:
2219+
- a quadratic objective
2220+
- 2 variables,
2221+
- and a single linear inequality constraint.
2222+
*/
2223+
func TestOptimizationProblem_ToLPStandardForm4(t *testing.T) {
2224+
// Constants
2225+
p1 := problem.NewProblem("TestOptimizationProblem_ToLPStandardForm4")
2226+
v1 := p1.AddVariable()
2227+
p1.AddVariable()
2228+
c1 := v1.LessEq(1.0)
2229+
2230+
p1.Constraints = append(p1.Constraints, c1)
2231+
2232+
// Create a quadratic objective
2233+
p1.Objective = *problem.NewObjective(
2234+
v1.Multiply(v1),
2235+
problem.SenseMaximize,
2236+
)
2237+
2238+
// Algorithm
2239+
_, _, err := p1.ToLPStandardForm1()
2240+
if err == nil {
2241+
t.Errorf("expected an error; received nil")
2242+
} else {
2243+
expectedError := mpiErrors.ProblemNotLinearError{
2244+
ProblemName: p1.Name,
2245+
Cause: causeOfProblemNonlinearity.Objective,
2246+
ConstraintIndex: -2,
2247+
}
2248+
if !strings.Contains(
2249+
err.Error(),
2250+
expectedError.Error(),
2251+
) {
2252+
t.Errorf("unexpected error: %v", err)
2253+
}
2254+
}
2255+
}
2256+
2257+
/*
2258+
TestOptimizationProblem_ToLPStandardForm5
2259+
Description:
2260+
2261+
This test verifies that the ToLPStandardForm function throws an error
2262+
when called on a problem that is not linear.
2263+
In this case, we will define a problem with a quadratic constraint.
2264+
The problem will have:
2265+
- a constant objective
2266+
- 2 variables,
2267+
- and a single quadratic inequality constraint.
2268+
*/
2269+
func TestOptimizationProblem_ToLPStandardForm5(t *testing.T) {
2270+
// Constants
2271+
p1 := problem.NewProblem("TestOptimizationProblem_ToLPStandardForm5")
2272+
v1 := p1.AddVariable()
2273+
p1.AddVariable()
2274+
c1 := v1.Multiply(v1).LessEq(1.0)
2275+
2276+
p1.Constraints = append(p1.Constraints, c1)
2277+
2278+
// Create good objective
2279+
p1.Objective = *problem.NewObjective(
2280+
symbolic.K(3.14),
2281+
problem.SenseMaximize,
2282+
)
2283+
2284+
// Algorithm
2285+
_, _, err := p1.ToLPStandardForm1()
2286+
if err == nil {
2287+
t.Errorf("expected an error; received nil")
2288+
} else {
2289+
expectedError := mpiErrors.ProblemNotLinearError{
2290+
ProblemName: p1.Name,
2291+
Cause: causeOfProblemNonlinearity.Constraint,
2292+
ConstraintIndex: 0,
2293+
}
2294+
if !strings.Contains(
2295+
err.Error(),
2296+
expectedError.Error(),
2297+
) {
2298+
t.Errorf("unexpected error: %v", err)
2299+
}
2300+
}
2301+
}

0 commit comments

Comments
 (0)