Skip to content

Commit 4c1b027

Browse files
Adding a new function which forces the standard LP to be in a specifi… (#13)
* Adding a new function which forces the standard LP to be in a specific optimization sense, if it is not already. * Added tests to try to catch when problem is not well-defined * Added tests for the CopyVariable() method * Updated SymbolicMath.go dependency * Upgraded dependencies again + tweaked some optimization problem tests to respect NEW expectations of ToLPStandardForm * Updated some tests * Removing unused cases in ToLPStandardForm1
1 parent c242cba commit 4c1b027

File tree

5 files changed

+322
-124
lines changed

5 files changed

+322
-124
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ toolchain go1.23.9
66

77
require gonum.org/v1/gonum v0.16.0
88

9-
require github.com/MatProGo-dev/SymbolicMath.go v0.2.3
9+
require github.com/MatProGo-dev/SymbolicMath.go v0.2.4-1

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
github.com/MatProGo-dev/SymbolicMath.go v0.2.2 h1:U9nLLgtslRXbweCsgW9uSw8AvoGMgjq2luQtXIuN3eA=
2-
github.com/MatProGo-dev/SymbolicMath.go v0.2.2/go.mod h1:tW8thj4pkaTV9lFNU3OCKmwQ3mZ2Eim6S4JpHRDfRvU=
3-
github.com/MatProGo-dev/SymbolicMath.go v0.2.3 h1:ffkUVU1oKzw2jj6fEu4BKW2YEYOWq55fwD7FOP9cY6k=
4-
github.com/MatProGo-dev/SymbolicMath.go v0.2.3/go.mod h1:tW8thj4pkaTV9lFNU3OCKmwQ3mZ2Eim6S4JpHRDfRvU=
1+
github.com/MatProGo-dev/SymbolicMath.go v0.2.4-1 h1:SIj6oFJgavWtArs8toeHCPfxOefGMplWSkNvlR9P2Ac=
2+
github.com/MatProGo-dev/SymbolicMath.go v0.2.4-1/go.mod h1:tW8thj4pkaTV9lFNU3OCKmwQ3mZ2Eim6S4JpHRDfRvU=
3+
github.com/MatProGo-dev/SymbolicMath.go v0.2.4 h1:SxvgOJBpx9H6ZHISyF3A79gOd1pHJd8Nywrqf4sJZTs=
4+
github.com/MatProGo-dev/SymbolicMath.go v0.2.4/go.mod h1:tW8thj4pkaTV9lFNU3OCKmwQ3mZ2Eim6S4JpHRDfRvU=
55
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
66
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package optimization_problem_errors
2+
3+
type NotWellDefinedError struct {
4+
ProblemName string
5+
ErrorSource error
6+
}
7+
8+
func (e NotWellDefinedError) Error() string {
9+
return "the problem " + e.ProblemName + " is not well defined: " + e.ErrorSource.Error()
10+
}

problem/optimization_problem.go

Lines changed: 95 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/MatProGo-dev/MatProInterface.go/causeOfProblemNonlinearity"
77
"github.com/MatProGo-dev/MatProInterface.go/mpiErrors"
8+
ope "github.com/MatProGo-dev/MatProInterface.go/mpiErrors/optimization_problem"
89
"github.com/MatProGo-dev/MatProInterface.go/optim"
910
getKVector "github.com/MatProGo-dev/SymbolicMath.go/get/KVector"
1011
"github.com/MatProGo-dev/SymbolicMath.go/symbolic"
@@ -660,21 +661,27 @@ Description:
660661
Where A is a matrix of coefficients, b is a vector of constants, and c is the vector of coefficients
661662
for the objective function. This method also returns the slack variables (i.e., the variables that
662663
are added to the problem to convert the inequalities into equalities).
664+
665+
Note:
666+
667+
This method will transform the vector or matrix constraints in the input problem
668+
into a set of scalar constraints. Thus, the number of constraints in your problem may
669+
"seem" to change.
663670
*/
664671
func (problemIn *OptimizationProblem) ToLPStandardForm1() (*OptimizationProblem, []symbolic.Variable, error) {
665672
// Input Processing
666673
err := problemIn.Check()
667674
if err != nil {
668-
return nil, nil, fmt.Errorf("the optimization problem is not well-formed: %v", err)
675+
return nil, nil, problemIn.MakeNotWellDefinedError()
669676
}
670677

671678
// Check if the problem is linear
672679
if !problemIn.IsLinear() {
673680
return nil, nil, problemIn.CheckIfLinear()
674681
}
675682

676-
// Setup
677-
problemWithAllPositiveVariables, err := problemIn.ToProblemWithAllPositiveVariables()
683+
// Change the problem so that it is written in terms of strictly positive variables
684+
problemWithAllPositiveVariables, err := problemIn.ToProblemWithAllPositiveVariables() // Note: This method may change the number of variables and constraints in the problem.
678685
if err != nil {
679686
return nil, nil, err
680687
}
@@ -708,94 +715,32 @@ func (problemIn *OptimizationProblem) ToLPStandardForm1() (*OptimizationProblem,
708715
case symbolic.SenseEqual:
709716
// No need to do anything
710717
case symbolic.SenseGreaterThanEqual:
711-
switch concreteConstraint := constraint.(type) {
712-
case symbolic.ScalarConstraint:
713-
// Add a new SCALAR slack variable to the right hand side
714-
problemInStandardForm.AddVariableClassic(0.0, symbolic.Infinity.Constant(), symbolic.Continuous)
715-
nVariables := len(problemInStandardForm.Variables)
716-
problemInStandardForm.Variables[nVariables-1].Name = problemInStandardForm.Variables[nVariables-1].Name + " (slack)"
717-
slackVariables = append(
718-
slackVariables,
719-
problemInStandardForm.Variables[nVariables-1],
720-
)
721-
722-
newRHS = newRHS.Plus(problemInStandardForm.Variables[nVariables-1])
723-
case symbolic.VectorConstraint:
724-
// Add a new VECTOR slack variable to the right hand side
725-
// TODO(Kwesi): Revisit this when we have a proper Len() method for constraints.
726-
dims := concreteConstraint.Dims()
727-
nRows := dims[0]
728-
problemInStandardForm.AddVariableVectorClassic(
729-
nRows,
730-
0.0,
731-
symbolic.Infinity.Constant(),
732-
symbolic.Continuous,
733-
)
734-
nVariables := len(problemInStandardForm.Variables)
735-
for jj := nRows - 1; jj >= 0; jj-- {
736-
problemInStandardForm.Variables[nVariables-1-jj].Name = problemInStandardForm.Variables[nVariables-1-jj].Name + " (slack)"
737-
slackVariables = append(
738-
slackVariables,
739-
problemInStandardForm.Variables[nVariables-1-jj],
740-
)
741-
}
742-
743-
// Add the slack variable to the right hand side
744-
newRHS = newRHS.Plus(
745-
symbolic.VariableVector(problemInStandardForm.Variables[nVariables-nRows : nVariables]),
746-
)
747-
default:
748-
return nil, nil, fmt.Errorf(
749-
"Unexpected constraint type: %T for \"ToStandardFormWithSlackVariables\" with %v sense",
750-
constraint,
751-
constraint.ConstrSense(),
752-
)
718+
// Constraints MUST be scalar at this point
719+
720+
// Add a new SCALAR slack variable to the right hand side
721+
problemInStandardForm.AddVariableClassic(0.0, symbolic.Infinity.Constant(), symbolic.Continuous)
722+
nVariables := len(problemInStandardForm.Variables)
723+
problemInStandardForm.Variables[nVariables-1].Name = problemInStandardForm.Variables[nVariables-1].Name + " (slack)"
724+
slackVariables = append(
725+
slackVariables,
726+
problemInStandardForm.Variables[nVariables-1],
727+
)
728+
729+
newRHS = newRHS.Plus(problemInStandardForm.Variables[nVariables-1])
753730

754-
}
755731
case symbolic.SenseLessThanEqual:
756-
// Use a switch statement to handle different dimensions of the constraint
757-
switch concreteConstraint := constraint.(type) {
758-
case symbolic.ScalarConstraint:
759-
// Add a new SCALAR slack variable to the left hand side
760-
problemInStandardForm.AddVariableClassic(0.0, symbolic.Infinity.Constant(), symbolic.Continuous)
761-
nVariables := len(problemInStandardForm.Variables)
762-
problemInStandardForm.Variables[nVariables-1].Name = problemInStandardForm.Variables[nVariables-1].Name + " (slack)"
763-
slackVariables = append(
764-
slackVariables,
765-
problemInStandardForm.Variables[nVariables-1],
766-
)
767-
newLHS = newLHS.Plus(problemInStandardForm.Variables[nVariables-1])
768-
case symbolic.VectorConstraint:
769-
// Add a new VECTOR slack variable to the left hand side
770-
// TODO(Kwesi): Revisit this when we have a proper Len() method for constraints.
771-
dims := concreteConstraint.Dims()
772-
nRows := dims[0]
773-
problemInStandardForm.AddVariableVectorClassic(
774-
nRows,
775-
0.0,
776-
symbolic.Infinity.Constant(),
777-
symbolic.Continuous,
778-
)
779-
nVariables := len(problemInStandardForm.Variables)
780-
for jj := nRows - 1; jj >= 0; jj-- {
781-
problemInStandardForm.Variables[nVariables-1-jj].Name = problemInStandardForm.Variables[nVariables-1-jj].Name + " (slack)"
782-
slackVariables = append(
783-
slackVariables,
784-
problemInStandardForm.Variables[nVariables-1-jj],
785-
)
786-
// fmt.Printf("Slack variable %d: %v\n", jj, problemInStandardForm.Variables[nVariables-1-jj])
787-
}
788-
// Add the slack variable to the left hand side
789-
newLHS = newLHS.Plus(
790-
symbolic.VariableVector(problemInStandardForm.Variables[nVariables-nRows : nVariables]),
791-
)
792-
default:
793-
return nil, nil, fmt.Errorf(
794-
"Unexpected constraint type %T for \"ToStandardFormWithSlackVariables\" with %v sense",
795-
constraint,
796-
constraint.ConstrSense(),
797-
)
798-
}
732+
// Constraints MUST be scalar at this point
733+
734+
// Add a new SCALAR slack variable to the left hand side
735+
problemInStandardForm.AddVariableClassic(0.0, symbolic.Infinity.Constant(), symbolic.Continuous)
736+
nVariables := len(problemInStandardForm.Variables)
737+
problemInStandardForm.Variables[nVariables-1].Name = problemInStandardForm.Variables[nVariables-1].Name + " (slack)"
738+
slackVariables = append(
739+
slackVariables,
740+
problemInStandardForm.Variables[nVariables-1],
741+
)
742+
newLHS = newLHS.Plus(problemInStandardForm.Variables[nVariables-1])
743+
799744
default:
800745
return nil, nil, fmt.Errorf(
801746
"Unknown constraint sense: " + constraint.ConstrSense().String(),
@@ -831,6 +776,54 @@ func (problemIn *OptimizationProblem) ToLPStandardForm1() (*OptimizationProblem,
831776
return problemInStandardForm, slackVariables, nil
832777
}
833778

779+
/*
780+
ToLPStandardForm2
781+
Description:
782+
783+
Transforms the given linear program (represented in an OptimizationProblem object)
784+
into a standard form (i.e., only linear equality constraints and a linear objective function).
785+
786+
max c^T * x
787+
subject to
788+
A * x = b
789+
x >= 0
790+
791+
Where:
792+
- A is a matrix of coefficients,
793+
- b is a vector of constants, and
794+
- c is the vector of coefficients for the objective function.
795+
This method also returns the slack variables (i.e., the variables that
796+
are added to the problem to convert the inequalities into equalities).
797+
*/
798+
func (problemIn *OptimizationProblem) ToLPStandardForm2() (*OptimizationProblem, []symbolic.Variable, error) {
799+
// Input Processing
800+
err := problemIn.Check()
801+
if err != nil {
802+
return nil, nil, problemIn.MakeNotWellDefinedError()
803+
}
804+
805+
// Use the existing method to convert to standard form 1
806+
problemInStandardForm, slackVariables, err := problemIn.ToLPStandardForm1()
807+
if err != nil {
808+
return nil, nil, err
809+
}
810+
811+
// Modify the objective function to be a maximization problem,
812+
// if it is not already.
813+
if problemInStandardForm.Objective.Sense == SenseMinimize {
814+
// If the problem is a minimization problem,
815+
// then we can convert it to a maximization problem by negating the objective function.
816+
newObjectiveExpression := problemInStandardForm.Objective.Expression.Multiply(-1.0)
817+
err = problemInStandardForm.SetObjective(newObjectiveExpression, SenseMaximize)
818+
if err != nil {
819+
return nil, nil, fmt.Errorf("there was a problem setting the new objective function: %v", err)
820+
}
821+
}
822+
823+
// Return the new problem and the slack variables
824+
return problemInStandardForm, slackVariables, nil
825+
}
826+
834827
/*
835828
WithAllPositiveVariableConstraintsRemoved
836829
Description:
@@ -851,30 +844,12 @@ func (op *OptimizationProblem) WithAllPositiveVariableConstraintsRemoved() *Opti
851844
newProblem.Variables = append(newProblem.Variables, variable)
852845
}
853846

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-
}
847+
// Reduce the constraints to scalar constraints
848+
scalarConstraints := symbolic.CompileConstraintsIntoScalarConstraints(op.Constraints)
849+
for _, constraintII := range scalarConstraints {
850+
// Check if the constraint is a (Non-negativity) x >= 0 or 0 <= x constraint
851+
if constraintII.IsNonnegativityConstraint() {
852+
continue
878853
}
879854

880855
// Otherwise, we can keep the constraint
@@ -1012,3 +987,10 @@ func (op *OptimizationProblem) SimplifyConstraints() {
1012987
// Set the new constraints
1013988
op.Constraints = newConstraints
1014989
}
990+
991+
func (op *OptimizationProblem) MakeNotWellDefinedError() ope.NotWellDefinedError {
992+
return ope.NotWellDefinedError{
993+
ProblemName: op.Name,
994+
ErrorSource: op.Check(),
995+
}
996+
}

0 commit comments

Comments
 (0)