Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ toolchain go1.23.9

require gonum.org/v1/gonum v0.16.0

require github.com/MatProGo-dev/SymbolicMath.go v0.2.3
require github.com/MatProGo-dev/SymbolicMath.go v0.2.4-1
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
github.com/MatProGo-dev/SymbolicMath.go v0.2.2 h1:U9nLLgtslRXbweCsgW9uSw8AvoGMgjq2luQtXIuN3eA=
github.com/MatProGo-dev/SymbolicMath.go v0.2.2/go.mod h1:tW8thj4pkaTV9lFNU3OCKmwQ3mZ2Eim6S4JpHRDfRvU=
github.com/MatProGo-dev/SymbolicMath.go v0.2.3 h1:ffkUVU1oKzw2jj6fEu4BKW2YEYOWq55fwD7FOP9cY6k=
github.com/MatProGo-dev/SymbolicMath.go v0.2.3/go.mod h1:tW8thj4pkaTV9lFNU3OCKmwQ3mZ2Eim6S4JpHRDfRvU=
github.com/MatProGo-dev/SymbolicMath.go v0.2.4-1 h1:SIj6oFJgavWtArs8toeHCPfxOefGMplWSkNvlR9P2Ac=
github.com/MatProGo-dev/SymbolicMath.go v0.2.4-1/go.mod h1:tW8thj4pkaTV9lFNU3OCKmwQ3mZ2Eim6S4JpHRDfRvU=
github.com/MatProGo-dev/SymbolicMath.go v0.2.4 h1:SxvgOJBpx9H6ZHISyF3A79gOd1pHJd8Nywrqf4sJZTs=
github.com/MatProGo-dev/SymbolicMath.go v0.2.4/go.mod h1:tW8thj4pkaTV9lFNU3OCKmwQ3mZ2Eim6S4JpHRDfRvU=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
10 changes: 10 additions & 0 deletions mpiErrors/optimization_problem/not_well_defined.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package optimization_problem_errors

type NotWellDefinedError struct {
ProblemName string
ErrorSource error
}

func (e NotWellDefinedError) Error() string {
return "the problem " + e.ProblemName + " is not well defined: " + e.ErrorSource.Error()
}
208 changes: 95 additions & 113 deletions problem/optimization_problem.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/MatProGo-dev/MatProInterface.go/causeOfProblemNonlinearity"
"github.com/MatProGo-dev/MatProInterface.go/mpiErrors"
ope "github.com/MatProGo-dev/MatProInterface.go/mpiErrors/optimization_problem"
"github.com/MatProGo-dev/MatProInterface.go/optim"
getKVector "github.com/MatProGo-dev/SymbolicMath.go/get/KVector"
"github.com/MatProGo-dev/SymbolicMath.go/symbolic"
Expand Down Expand Up @@ -660,21 +661,27 @@ Description:
Where A is a matrix of coefficients, b is a vector of constants, and c is the vector of coefficients
for the objective function. This method also returns the slack variables (i.e., the variables that
are added to the problem to convert the inequalities into equalities).

Note:

This method will transform the vector or matrix constraints in the input problem
into a set of scalar constraints. Thus, the number of constraints in your problem may
"seem" to change.
*/
func (problemIn *OptimizationProblem) ToLPStandardForm1() (*OptimizationProblem, []symbolic.Variable, error) {
// Input Processing
err := problemIn.Check()
if err != nil {
return nil, nil, fmt.Errorf("the optimization problem is not well-formed: %v", err)
return nil, nil, problemIn.MakeNotWellDefinedError()
}

// Check if the problem is linear
if !problemIn.IsLinear() {
return nil, nil, problemIn.CheckIfLinear()
}

// Setup
problemWithAllPositiveVariables, err := problemIn.ToProblemWithAllPositiveVariables()
// Change the problem so that it is written in terms of strictly positive variables
problemWithAllPositiveVariables, err := problemIn.ToProblemWithAllPositiveVariables() // Note: This method may change the number of variables and constraints in the problem.
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -708,94 +715,32 @@ func (problemIn *OptimizationProblem) ToLPStandardForm1() (*OptimizationProblem,
case symbolic.SenseEqual:
// No need to do anything
case symbolic.SenseGreaterThanEqual:
switch concreteConstraint := constraint.(type) {
case symbolic.ScalarConstraint:
// Add a new SCALAR slack variable to the right hand side
problemInStandardForm.AddVariableClassic(0.0, symbolic.Infinity.Constant(), symbolic.Continuous)
nVariables := len(problemInStandardForm.Variables)
problemInStandardForm.Variables[nVariables-1].Name = problemInStandardForm.Variables[nVariables-1].Name + " (slack)"
slackVariables = append(
slackVariables,
problemInStandardForm.Variables[nVariables-1],
)

newRHS = newRHS.Plus(problemInStandardForm.Variables[nVariables-1])
case symbolic.VectorConstraint:
// Add a new VECTOR slack variable to the right hand side
// TODO(Kwesi): Revisit this when we have a proper Len() method for constraints.
dims := concreteConstraint.Dims()
nRows := dims[0]
problemInStandardForm.AddVariableVectorClassic(
nRows,
0.0,
symbolic.Infinity.Constant(),
symbolic.Continuous,
)
nVariables := len(problemInStandardForm.Variables)
for jj := nRows - 1; jj >= 0; jj-- {
problemInStandardForm.Variables[nVariables-1-jj].Name = problemInStandardForm.Variables[nVariables-1-jj].Name + " (slack)"
slackVariables = append(
slackVariables,
problemInStandardForm.Variables[nVariables-1-jj],
)
}

// Add the slack variable to the right hand side
newRHS = newRHS.Plus(
symbolic.VariableVector(problemInStandardForm.Variables[nVariables-nRows : nVariables]),
)
default:
return nil, nil, fmt.Errorf(
"Unexpected constraint type: %T for \"ToStandardFormWithSlackVariables\" with %v sense",
constraint,
constraint.ConstrSense(),
)
// Constraints MUST be scalar at this point

// Add a new SCALAR slack variable to the right hand side
problemInStandardForm.AddVariableClassic(0.0, symbolic.Infinity.Constant(), symbolic.Continuous)
nVariables := len(problemInStandardForm.Variables)
problemInStandardForm.Variables[nVariables-1].Name = problemInStandardForm.Variables[nVariables-1].Name + " (slack)"
slackVariables = append(
slackVariables,
problemInStandardForm.Variables[nVariables-1],
)

newRHS = newRHS.Plus(problemInStandardForm.Variables[nVariables-1])

}
case symbolic.SenseLessThanEqual:
// Use a switch statement to handle different dimensions of the constraint
switch concreteConstraint := constraint.(type) {
case symbolic.ScalarConstraint:
// Add a new SCALAR slack variable to the left hand side
problemInStandardForm.AddVariableClassic(0.0, symbolic.Infinity.Constant(), symbolic.Continuous)
nVariables := len(problemInStandardForm.Variables)
problemInStandardForm.Variables[nVariables-1].Name = problemInStandardForm.Variables[nVariables-1].Name + " (slack)"
slackVariables = append(
slackVariables,
problemInStandardForm.Variables[nVariables-1],
)
newLHS = newLHS.Plus(problemInStandardForm.Variables[nVariables-1])
case symbolic.VectorConstraint:
// Add a new VECTOR slack variable to the left hand side
// TODO(Kwesi): Revisit this when we have a proper Len() method for constraints.
dims := concreteConstraint.Dims()
nRows := dims[0]
problemInStandardForm.AddVariableVectorClassic(
nRows,
0.0,
symbolic.Infinity.Constant(),
symbolic.Continuous,
)
nVariables := len(problemInStandardForm.Variables)
for jj := nRows - 1; jj >= 0; jj-- {
problemInStandardForm.Variables[nVariables-1-jj].Name = problemInStandardForm.Variables[nVariables-1-jj].Name + " (slack)"
slackVariables = append(
slackVariables,
problemInStandardForm.Variables[nVariables-1-jj],
)
// fmt.Printf("Slack variable %d: %v\n", jj, problemInStandardForm.Variables[nVariables-1-jj])
}
// Add the slack variable to the left hand side
newLHS = newLHS.Plus(
symbolic.VariableVector(problemInStandardForm.Variables[nVariables-nRows : nVariables]),
)
default:
return nil, nil, fmt.Errorf(
"Unexpected constraint type %T for \"ToStandardFormWithSlackVariables\" with %v sense",
constraint,
constraint.ConstrSense(),
)
}
// Constraints MUST be scalar at this point

// Add a new SCALAR slack variable to the left hand side
problemInStandardForm.AddVariableClassic(0.0, symbolic.Infinity.Constant(), symbolic.Continuous)
nVariables := len(problemInStandardForm.Variables)
problemInStandardForm.Variables[nVariables-1].Name = problemInStandardForm.Variables[nVariables-1].Name + " (slack)"
slackVariables = append(
slackVariables,
problemInStandardForm.Variables[nVariables-1],
)
newLHS = newLHS.Plus(problemInStandardForm.Variables[nVariables-1])

default:
return nil, nil, fmt.Errorf(
"Unknown constraint sense: " + constraint.ConstrSense().String(),
Expand Down Expand Up @@ -831,6 +776,54 @@ func (problemIn *OptimizationProblem) ToLPStandardForm1() (*OptimizationProblem,
return problemInStandardForm, slackVariables, nil
}

/*
ToLPStandardForm2
Description:

Transforms the given linear program (represented in an OptimizationProblem object)
into a standard form (i.e., only linear equality constraints and a linear objective function).

max c^T * x
subject to
A * x = b
x >= 0

Where:
- A is a matrix of coefficients,
- b is a vector of constants, and
- c is the vector of coefficients for the objective function.
This method also returns the slack variables (i.e., the variables that
are added to the problem to convert the inequalities into equalities).
*/
func (problemIn *OptimizationProblem) ToLPStandardForm2() (*OptimizationProblem, []symbolic.Variable, error) {
// Input Processing
err := problemIn.Check()
if err != nil {
return nil, nil, problemIn.MakeNotWellDefinedError()
}

// Use the existing method to convert to standard form 1
problemInStandardForm, slackVariables, err := problemIn.ToLPStandardForm1()
if err != nil {
return nil, nil, err
}

// Modify the objective function to be a maximization problem,
// if it is not already.
if problemInStandardForm.Objective.Sense == SenseMinimize {
Copy link

Copilot AI Aug 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The objective function transformation lacks documentation explaining why the expression is multiplied by -1. This is a critical mathematical transformation that converts min c^Tx to max (-c)^Tx while preserving optimal solutions, and should be documented with a comment.

Copilot uses AI. Check for mistakes.
// If the problem is a minimization problem,
// then we can convert it to a maximization problem by negating the objective function.
newObjectiveExpression := problemInStandardForm.Objective.Expression.Multiply(-1.0)
err = problemInStandardForm.SetObjective(newObjectiveExpression, SenseMaximize)
if err != nil {
return nil, nil, fmt.Errorf("there was a problem setting the new objective function: %v", err)
}
}

// Return the new problem and the slack variables
return problemInStandardForm, slackVariables, nil
}

/*
WithAllPositiveVariableConstraintsRemoved
Description:
Expand All @@ -851,30 +844,12 @@ func (op *OptimizationProblem) WithAllPositiveVariableConstraintsRemoved() *Opti
newProblem.Variables = append(newProblem.Variables, variable)
}

// Copy the constraints
for _, constraintII := range op.Constraints {
// Check if the constraint is a x >= 0 constraint
if symbolic.SenseGreaterThanEqual == constraintII.ConstrSense() {
lhsContains1Variable := len(constraintII.Left().Variables()) == 1
rhs, rhsIsConstant := constraintII.Right().(symbolic.K)
if lhsContains1Variable && rhsIsConstant {
if float64(rhs) == 0.0 {
// If the constraint is of the form x >= 0, we can remove it
continue
}
}
}

// Check if the constraint is a 0 <= x constraint
if symbolic.SenseLessThanEqual == constraintII.ConstrSense() {
rhsContains1Variable := len(constraintII.Left().Variables()) == 1
lhs, lhsIsConstant := constraintII.Right().(symbolic.K)
if rhsContains1Variable && lhsIsConstant {
if float64(lhs) == 0.0 {
// If the constraint is of the form 0 <= x, we can remove it
continue
}
}
// Reduce the constraints to scalar constraints
scalarConstraints := symbolic.CompileConstraintsIntoScalarConstraints(op.Constraints)
for _, constraintII := range scalarConstraints {
// Check if the constraint is a (Non-negativity) x >= 0 or 0 <= x constraint
if constraintII.IsNonnegativityConstraint() {
continue
}

// Otherwise, we can keep the constraint
Expand Down Expand Up @@ -1012,3 +987,10 @@ func (op *OptimizationProblem) SimplifyConstraints() {
// Set the new constraints
op.Constraints = newConstraints
}

func (op *OptimizationProblem) MakeNotWellDefinedError() ope.NotWellDefinedError {
return ope.NotWellDefinedError{
ProblemName: op.Name,
ErrorSource: op.Check(),
}
}
Loading
Loading