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.2
require github.com/MatProGo-dev/SymbolicMath.go v0.2.3
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,4 +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=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
11 changes: 11 additions & 0 deletions problem/objective.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,14 @@ Description:
func (o *Objective) IsLinear() bool {
return symbolic.IsLinear(o.Expression)
}

/*
SubstituteAccordingTo
Description:

Substitutes the variables in the objective according to the replacement map.
*/
func (o *Objective) SubstituteAccordingTo(replacementMap map[symbolic.Variable]symbolic.Expression) *Objective {
newExpression := o.Expression.SubstituteAccordingTo(replacementMap)
return &Objective{newExpression, o.Sense}
}
104 changes: 102 additions & 2 deletions problem/optimization_problem.go
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,26 @@ func (op *OptimizationProblem) LinearEqualityConstraintMatrices() (symbolic.KMat
return COut2, dOut2, nil
}

// func (op *OptimizationProblem) Simplify() OptimizationProblem {
// // Create a new optimization problem
// newProblem := NewProblem(op.Name + " (Simplified)")

// // Add all variables to the new problem
// for _, variable := range op.Variables {
// newProblem.Variables = append(newProblem.Variables, variable)
// }

// // Add all constraints to the new problem
// for _, constraint := range op.Constraints {
// newProblem.Constraints = append(newProblem.Constraints, constraint)
// }

// // Set the objective of the new problem
// newProblem.Objective = op.Objective

// return newProblem
// }

/*
ToProblemWithAllPositiveVariables
Description:
Expand Down Expand Up @@ -668,8 +688,6 @@ func (problemIn *OptimizationProblem) ToLPStandardForm1() (*OptimizationProblem,
problemIn.Name + " (In Standard Form)",
)

// Copy over each of the

// Add all variables to the new problem
mapFromInToNewVariables := make(map[symbolic.Variable]symbolic.Expression)
for _, varII := range problemWithAllPositiveVariables.Variables {
Expand Down Expand Up @@ -857,3 +875,85 @@ func (op *OptimizationProblem) CheckIfLinear() error {
// All Checks Passed!
return nil
}

/*
CopyVariable
Description:

Creates a deep copy of the given variable within
the optimization problem.
*/
func (op *OptimizationProblem) CopyVariable(variable symbolic.Variable) symbolic.Variable {
// Setup
newVariable := variable
newVariable.Name = fmt.Sprintf("%s (copy)", variable.Name)

// Assign a new, unique ID to the variable
maxID := uint64(0)
for _, v := range op.Variables {
if v.ID >= maxID {
maxID = v.ID + 1
}
}
newVariable.ID = maxID

// Add the new variable to the problem
op.Variables = append(op.Variables, newVariable)
return newVariable
}

/*
Copy
Description:

Returns a deep copy of the optimization problem.
*/
func (op *OptimizationProblem) Copy() *OptimizationProblem {
// Setup
newProblem := NewProblem(op.Name)

// Copy Variables
replacementMap := make(map[symbolic.Variable]symbolic.Expression)
for _, variable := range op.Variables {
newVariable := newProblem.CopyVariable(variable)
replacementMap[variable] = newVariable
}

// Copy Constraints
for _, constraint := range op.Constraints {
newConstraint := constraint.SubstituteAccordingTo(replacementMap)
newProblem.Constraints = append(newProblem.Constraints, newConstraint)
}

// Copy Objective
newProblem.Objective = *op.Objective.SubstituteAccordingTo(replacementMap)

// Return the new problem
return newProblem
}

/*
SimplifyConstraints
Description:

This method simplifies the constraints of the optimization problem by removing redundant constraints.
*/
func (op *OptimizationProblem) SimplifyConstraints() {
// Setup
newConstraints := make([]symbolic.Constraint, 0)

// Iterate through all constraints and check if they are redundant
for _, constraint := range op.Constraints {
// Determine if the newConstraints imply that
// the current constraint is also satisfied.
if ConstraintIsRedundantGivenOthers(constraint, newConstraints) {
continue
}

// Otherwise, add the constraint to the newConstraints
newConstraints = append(newConstraints, constraint)
}

// Set the new constraints
op.Constraints = newConstraints
}
17 changes: 17 additions & 0 deletions problem/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package problem

import "github.com/MatProGo-dev/SymbolicMath.go/symbolic"

func ConstraintIsRedundantGivenOthers(
constraint symbolic.Constraint,
constraints []symbolic.Constraint,
) bool {
// Check if the expression can be derived from the constraints
for _, c := range constraints {
if c.ImpliesThisIsAlsoSatisfied(constraint) {
return true
}
}

return false
}
158 changes: 158 additions & 0 deletions testing/problem/optimization_problem_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2624,3 +2624,161 @@ func TestOptimizationProblem_CheckIfLinear1(t *testing.T) {
}
}
}

/*
TestOptimizationProblem_SimplifyConstraints1
Description:

This test verifies that the SimplifyConstraints function properly simplifies
a problem with redundant constraints.
The problem will have:
- a constant objective
- 1 variable1,
- and 2 linear inequality constraints:
x1 <= 1
x1 <= 2
The second constraint is redundant and should be removed.
The result should be a problem with 1 variable and 1 constraint.
*/
func TestOptimizationProblem_SimplifyConstraints1(t *testing.T) {
// Constants
p1 := problem.NewProblem("TestOptimizationProblem_SimplifyConstraints1")
v1 := p1.AddVariable()
c1 := v1.LessEq(1.0)
c2 := v1.LessEq(2.0)

p1.Constraints = append(p1.Constraints, c1)
p1.Constraints = append(p1.Constraints, c2)

// Create good objective
p1.Objective = *problem.NewObjective(
symbolic.K(3.14),
problem.SenseMaximize,
)

// Create a copy of p1 to compare against after simplification
p2 := p1.Copy()

// Algorithm
p1.SimplifyConstraints()

// Check that the number of variables is as expected.
if len(p2.Variables) != len(p1.Variables) {
t.Errorf("expected the number of variables to be %v; received %v",
len(p1.Variables), len(p2.Variables))
}

// Check that the number of constraints is as expected.
if len(p1.Constraints) != 1 {
t.Errorf("expected the new number of constraints to be %v; received %v",
1, len(p1.Constraints))
}

// Check that the remaining constraint is the expected one.
p1FirstConstraint := p1.Constraints[0]
L1 := p1FirstConstraint.Left().(symbolic.ScalarExpression).LinearCoeff(p1.Variables)
p2FirstConstraint := p2.Constraints[0]
L2 := p2FirstConstraint.Left().(symbolic.ScalarExpression).LinearCoeff(p2.Variables)

if L1.AtVec(0) != L2.AtVec(0) {
t.Errorf("expected the remaining constraint's coefficient to be %v; received %v",
L1, L2)
}
}

/*
TestOptimizationProblem_SimplifyConstraints2
Description:

This test verifies that the SimplifyConstraints function properly handles
a problem with no constraints.
The problem will have:
- a constant objective
- 1 variable,
- and no constraints.
The result should be a problem with 1 variable and no constraints.
*/
func TestOptimizationProblem_SimplifyConstraints2(t *testing.T) {
// Constants
p1 := problem.NewProblem("TestOptimizationProblem_SimplifyConstraints2")

// Create good objective
p1.Objective = *problem.NewObjective(
symbolic.K(3.14),
problem.SenseMaximize,
)

// Create a copy of p1 to compare against after simplification
p2 := p1.Copy()

// Algorithm
p1.SimplifyConstraints()

// Check that the number of variables is as expected.
if len(p2.Variables) != len(p1.Variables) {
t.Errorf("expected the number of variables to be %v; received %v",
len(p1.Variables), len(p2.Variables))
}

// Check that the number of constraints is as expected.
if len(p1.Constraints) != 0 {
t.Errorf("expected the new number of constraints to be %v; received %v",
0, len(p1.Constraints))
}
}

/*
TestOptimizationProblem_SimplifyConstraints3
Description:

This test verifies that the SimplifyConstraints function properly handles
a problem with a single constraint that is not redundant.
The problem will have:
- a constant objective
- 1 variable,
- and a single linear inequality constraint.
The result should be a problem with 1 variable and 1 constraint.
*/
func TestOptimizationProblem_SimplifyConstraints3(t *testing.T) {
// Constants
p1 := problem.NewProblem("TestOptimizationProblem_SimplifyConstraints3")
v1 := p1.AddVariable()
c1 := v1.LessEq(1.0)

p1.Constraints = append(p1.Constraints, c1)

// Create good objective
p1.Objective = *problem.NewObjective(
symbolic.K(3.14),
problem.SenseMaximize,
)

// Create a copy of p1 to compare against after simplification
p2 := p1.Copy()

// Algorithm
p1.SimplifyConstraints()

// Check that the number of variables is as expected.
if len(p2.Variables) != len(p1.Variables) {
t.Errorf("expected the number of variables to be %v; received %v",
len(p1.Variables), len(p2.Variables))
}

// Check that the number of constraints is as expected.
if len(p1.Constraints) != 1 {
t.Errorf("expected the new number of constraints to be %v; received %v",
1, len(p1.Constraints))
}

// Check that the remaining constraint is the expected one.
p1FirstConstraint := p1.Constraints[0]
L1 := p1FirstConstraint.Left().(symbolic.ScalarExpression).LinearCoeff(p1.Variables)
p2FirstConstraint := p2.Constraints[0]
L2 := p2FirstConstraint.Left().(symbolic.ScalarExpression).LinearCoeff(p2.Variables)

if L1.AtVec(0) != L2.AtVec(0) {
t.Errorf("expected the remaining constraint's coefficient to be %v; received %v",
L1, L2)
}
}
Loading