Skip to content

Commit c242cba

Browse files
Creating a bit of logic to avoid creating extra variables if possible (#12)
* Creating a bit of logic to avoid creating extra variables if possible * Introduced new examples and improved test coverage for some of the tests of all positive variables * Adding a method for cleaning up LPs during building * Added simplify step to constructing of problem
1 parent 250db11 commit c242cba

3 files changed

Lines changed: 357 additions & 45 deletions

File tree

problem/examples.go

Lines changed: 100 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ Description:
1818
x1 + 2 x2 + 2 x3 <= 4
1919
3 x1 + 4 x3 <= 6
2020
2 x1 + x2 + 4 x3 <= 8
21-
x1 >= 0
22-
x2 >= 0
23-
x3 >= 0
21+
x1 >= -1.0
22+
x2 >= -1.0
23+
x3 >= -1.0
2424
*/
2525
func GetExampleProblem3() *OptimizationProblem {
2626
// Setup
@@ -29,7 +29,7 @@ func GetExampleProblem3() *OptimizationProblem {
2929
// Create variables
3030
x := out.AddVariableVectorClassic(
3131
3,
32-
0.0,
32+
-1.0,
3333
symbolic.Infinity.Constant(),
3434
symbolic.Continuous,
3535
)
@@ -60,3 +60,99 @@ func GetExampleProblem3() *OptimizationProblem {
6060
// }
6161
return out
6262
}
63+
64+
/*
65+
GetExampleProblem4
66+
Description:
67+
68+
Returns the LP from this youtube video:
69+
https://www.youtube.com/watch?v=QAR8zthQypc&t=483s
70+
It should look like this:
71+
Maximize 4 x1 + 3 x2 + 5 x3
72+
Subject to
73+
x1 + 2 x2 + 2 x3 <= 4
74+
3 x1 + 4 x3 <= 6
75+
2 x1 + x2 + 4 x3 <= 8
76+
x1 >= 0
77+
x2 >= 0
78+
x3 >= 0
79+
*/
80+
func GetExampleProblem4() *OptimizationProblem {
81+
// Setup
82+
out := NewProblem("TestProblem3")
83+
84+
// Create variables
85+
x := out.AddVariableVectorClassic(
86+
3,
87+
0.0, // Use this line to implement non-negativity constraints
88+
symbolic.Infinity.Constant(),
89+
symbolic.Continuous,
90+
)
91+
92+
// Create Basic Objective
93+
c := getKVector.From([]float64{4.0, 3.0, 5.0})
94+
out.SetObjective(
95+
c.Transpose().Multiply(x),
96+
SenseMaximize,
97+
)
98+
99+
// Create Constraints (using one big matrix)
100+
A := getKMatrix.From([][]float64{
101+
{1.0, 2.0, 2.0},
102+
{3.0, 0.0, 4.0},
103+
{2.0, 1.0, 4.0},
104+
})
105+
b := getKVector.From([]float64{4.0, 6.0, 8.0})
106+
out.Constraints = append(out.Constraints, A.Multiply(x).LessEq(b))
107+
108+
return out
109+
}
110+
111+
/*
112+
GetExampleProblem5
113+
Description:
114+
115+
Returns the LP from this youtube video:
116+
https://www.youtube.com/watch?v=QAR8zthQypc&t=483s
117+
It should look like this:
118+
Maximize 4 x1 + 3 x2 + 5 x3
119+
Subject to
120+
x1 + 2 x2 + 2 x3 <= 4
121+
3 x1 + 4 x3 <= 6
122+
2 x1 + x2 + 4 x3 <= 8
123+
x1 >= 0
124+
x2 >= 0
125+
x3 >= 0
126+
*/
127+
func GetExampleProblem5() *OptimizationProblem {
128+
// Setup
129+
out := NewProblem("TestProblem3")
130+
131+
// Create variables
132+
x := out.AddVariableVector(3)
133+
134+
// Create Basic Objective
135+
c := getKVector.From([]float64{4.0, 3.0, 5.0})
136+
out.SetObjective(
137+
c.Transpose().Multiply(x),
138+
SenseMaximize,
139+
)
140+
141+
// Create Constraints (using one big matrix)
142+
A := getKMatrix.From([][]float64{
143+
{1.0, 2.0, 2.0},
144+
{3.0, 0.0, 4.0},
145+
{2.0, 1.0, 4.0},
146+
})
147+
b := getKVector.From([]float64{4.0, 6.0, 8.0})
148+
out.Constraints = append(out.Constraints, A.Multiply(x).LessEq(b))
149+
150+
// Add non-negativity constraints
151+
for _, varII := range x {
152+
out.Constraints = append(
153+
out.Constraints,
154+
varII.GreaterEq(0.0),
155+
)
156+
}
157+
return out
158+
}

problem/optimization_problem.go

Lines changed: 87 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -568,26 +568,6 @@ func (op *OptimizationProblem) LinearEqualityConstraintMatrices() (symbolic.KMat
568568
return COut2, dOut2, nil
569569
}
570570

571-
// func (op *OptimizationProblem) Simplify() OptimizationProblem {
572-
// // Create a new optimization problem
573-
// newProblem := NewProblem(op.Name + " (Simplified)")
574-
575-
// // Add all variables to the new problem
576-
// for _, variable := range op.Variables {
577-
// newProblem.Variables = append(newProblem.Variables, variable)
578-
// }
579-
580-
// // Add all constraints to the new problem
581-
// for _, constraint := range op.Constraints {
582-
// newProblem.Constraints = append(newProblem.Constraints, constraint)
583-
// }
584-
585-
// // Set the objective of the new problem
586-
// newProblem.Objective = op.Objective
587-
588-
// return newProblem
589-
// }
590-
591571
/*
592572
ToProblemWithAllPositiveVariables
593573
Description:
@@ -602,6 +582,7 @@ Description:
602582
func (op *OptimizationProblem) ToProblemWithAllPositiveVariables() (*OptimizationProblem, error) {
603583
// Setup
604584
newProblem := NewProblem(op.Name + " (All Positive Variables)")
585+
epsMagic := 1e-8 // TODO(Kwesi): Make this a parameter OR a constant in the package.
605586

606587
// For each variable, let's create two new variables
607588
// and set the original variable to be the difference of the two
@@ -610,20 +591,35 @@ func (op *OptimizationProblem) ToProblemWithAllPositiveVariables() (*Optimizatio
610591
// Setup
611592
xII := op.Variables[ii]
612593

594+
// Expression for the positive and negative parts
595+
var expressionForReplacement symbolic.Expression = symbolic.K(0.0)
596+
613597
// Create the two new variables
614-
newProblem.AddVariableClassic(0.0, symbolic.Infinity.Constant(), symbolic.Continuous)
615-
nVariables := len(newProblem.Variables)
616-
newProblem.Variables[nVariables-1].Name = xII.Name + " (+)"
617-
variablePositivePart := newProblem.Variables[nVariables-1]
598+
// - Positive Part
599+
positivePartExists := xII.Upper >= 0
600+
positivePartExists = positivePartExists && !ConstraintIsRedundantGivenOthers(xII.LessEq(0.0-epsMagic), op.Constraints)
601+
if positivePartExists {
602+
newProblem.AddVariableClassic(0.0, symbolic.Infinity.Constant(), symbolic.Continuous)
603+
nVariables := len(newProblem.Variables)
604+
newProblem.Variables[nVariables-1].Name = xII.Name + " (+)"
605+
variablePositivePart := newProblem.Variables[nVariables-1]
606+
expressionForReplacement = expressionForReplacement.Plus(variablePositivePart)
607+
}
618608

619-
newProblem.AddVariableClassic(0.0, symbolic.Infinity.Constant(), symbolic.Continuous)
620-
nVariables = len(newProblem.Variables)
621-
newProblem.Variables[nVariables-1].Name = xII.Name + " (-)"
622-
variableNegativePart := newProblem.Variables[nVariables-1]
609+
// - Negative Part
610+
negativePartExists := xII.Lower < 0
611+
negativePartExists = negativePartExists && !ConstraintIsRedundantGivenOthers(xII.GreaterEq(0.0), op.Constraints)
612+
if negativePartExists {
613+
newProblem.AddVariableClassic(0.0, symbolic.Infinity.Constant(), symbolic.Continuous)
614+
nVariables := len(newProblem.Variables)
615+
newProblem.Variables[nVariables-1].Name = xII.Name + " (-)"
616+
variableNegativePart := newProblem.Variables[nVariables-1]
617+
618+
expressionForReplacement = expressionForReplacement.Minus(variableNegativePart)
619+
}
623620

624621
// Set the original variable to be the difference of the two new variables
625-
mapFromOriginalVariablesToNewExpressions[xII] =
626-
variablePositivePart.Minus(variableNegativePart)
622+
mapFromOriginalVariablesToNewExpressions[xII] = expressionForReplacement
627623
}
628624

629625
// Now, let's create the new constraints by replacing the variables in the
@@ -697,8 +693,9 @@ func (problemIn *OptimizationProblem) ToLPStandardForm1() (*OptimizationProblem,
697693
}
698694

699695
// Add all constraints to the new problem
696+
problemWithPositivesAndCleanedConstraints := problemWithAllPositiveVariables.WithAllPositiveVariableConstraintsRemoved()
700697
slackVariables := []symbolic.Variable{}
701-
for _, constraint := range problemWithAllPositiveVariables.Constraints {
698+
for _, constraint := range problemWithPositivesAndCleanedConstraints.Constraints {
702699
// Create a new expression by substituting the variables according
703700
// to the map we created above
704701
oldLHS := constraint.Left()
@@ -827,12 +824,70 @@ func (problemIn *OptimizationProblem) ToLPStandardForm1() (*OptimizationProblem,
827824
problemWithAllPositiveVariables.Objective.Sense,
828825
)
829826

830-
// fmt.Printf("The slack variables are: %v\n", slackVariables)
827+
// Simplify The Constraints If Possible
828+
problemInStandardForm.SimplifyConstraints()
831829

832830
// Return the new problem and the slack variables
833831
return problemInStandardForm, slackVariables, nil
834832
}
835833

834+
/*
835+
WithAllPositiveVariableConstraintsRemoved
836+
Description:
837+
838+
Returns a new optimization problem that is the same as the original problem
839+
but with all constraints of the following form removed:
840+
x >= 0
841+
0 <= x
842+
Where x is a variable in the problem.
843+
This is useful for removing redundant constraints that are already implied by the variable bounds.
844+
*/
845+
func (op *OptimizationProblem) WithAllPositiveVariableConstraintsRemoved() *OptimizationProblem {
846+
// Setup
847+
newProblem := NewProblem(op.Name)
848+
849+
// Copy the variables
850+
for _, variable := range op.Variables {
851+
newProblem.Variables = append(newProblem.Variables, variable)
852+
}
853+
854+
// Copy the constraints
855+
for _, constraintII := range op.Constraints {
856+
// Check if the constraint is a x >= 0 constraint
857+
if symbolic.SenseGreaterThanEqual == constraintII.ConstrSense() {
858+
lhsContains1Variable := len(constraintII.Left().Variables()) == 1
859+
rhs, rhsIsConstant := constraintII.Right().(symbolic.K)
860+
if lhsContains1Variable && rhsIsConstant {
861+
if float64(rhs) == 0.0 {
862+
// If the constraint is of the form x >= 0, we can remove it
863+
continue
864+
}
865+
}
866+
}
867+
868+
// Check if the constraint is a 0 <= x constraint
869+
if symbolic.SenseLessThanEqual == constraintII.ConstrSense() {
870+
rhsContains1Variable := len(constraintII.Left().Variables()) == 1
871+
lhs, lhsIsConstant := constraintII.Right().(symbolic.K)
872+
if rhsContains1Variable && lhsIsConstant {
873+
if float64(lhs) == 0.0 {
874+
// If the constraint is of the form 0 <= x, we can remove it
875+
continue
876+
}
877+
}
878+
}
879+
880+
// Otherwise, we can keep the constraint
881+
newProblem.Constraints = append(newProblem.Constraints, constraintII)
882+
}
883+
884+
// Copy the objective
885+
newProblem.Objective = op.Objective
886+
887+
// Return the new problem
888+
return newProblem
889+
}
890+
836891
/*
837892
CheckIfLinear
838893
Description:

0 commit comments

Comments
 (0)