From dfc9e4e1820105fcb6717f6a90d379122c2a82d6 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Wed, 8 Oct 2025 00:28:40 -0400 Subject: [PATCH 1/6] Upgrading SymbolicMath.go --- go.mod | 2 +- go.sum | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index fa164af..2c1ffb6 100644 --- a/go.mod +++ b/go.mod @@ -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.5 +require github.com/MatProGo-dev/SymbolicMath.go v0.2.6 diff --git a/go.sum b/go.sum index 42d8405..d1772a3 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,4 @@ -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= -github.com/MatProGo-dev/SymbolicMath.go v0.2.5 h1:fGpwtywb2hUXvGKk1Te6PQEfHeVar5w05KTbc3wHj6A= -github.com/MatProGo-dev/SymbolicMath.go v0.2.5/go.mod h1:tW8thj4pkaTV9lFNU3OCKmwQ3mZ2Eim6S4JpHRDfRvU= +github.com/MatProGo-dev/SymbolicMath.go v0.2.6 h1:0THkOKIjdjadIb9MHIUflk08U7tv17KvtQPOP3eMOfk= +github.com/MatProGo-dev/SymbolicMath.go v0.2.6/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= From 937be8fb015c05015368478c61a109e3bfee4d5c Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Wed, 8 Oct 2025 00:45:44 -0400 Subject: [PATCH 2/6] Updated all function signatures to include maps to the new variables that we care about --- problem/optimization_problem.go | 48 +++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/problem/optimization_problem.go b/problem/optimization_problem.go index b87f46a..df4d774 100644 --- a/problem/optimization_problem.go +++ b/problem/optimization_problem.go @@ -580,7 +580,7 @@ Description: Then, we replace every instance of the original variable with the difference of the two new variables (x = x_+ - x_-). */ -func (op *OptimizationProblem) ToProblemWithAllPositiveVariables() (*OptimizationProblem, error) { +func (op *OptimizationProblem) ToProblemWithAllPositiveVariables() (*OptimizationProblem, map[symbolic.Variable]symbolic.Expression, error) { // Setup newProblem := NewProblem(op.Name + " (All Positive Variables)") epsMagic := 1e-8 // TODO(Kwesi): Make this a parameter OR a constant in the package. @@ -643,7 +643,7 @@ func (op *OptimizationProblem) ToProblemWithAllPositiveVariables() (*Optimizatio op.Objective.Sense, ) - return newProblem, nil + return newProblem, mapFromOriginalVariablesToNewExpressions, nil } /* @@ -668,22 +668,22 @@ Note: 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) { +func (problemIn *OptimizationProblem) ToLPStandardForm1() (*OptimizationProblem, []symbolic.Variable, map[symbolic.Variable]symbolic.Expression, error) { // Input Processing err := problemIn.Check() if err != nil { - return nil, nil, problemIn.MakeNotWellDefinedError() + return nil, nil, nil, problemIn.MakeNotWellDefinedError() } // Check if the problem is linear if !problemIn.IsLinear() { - return nil, nil, problemIn.CheckIfLinear() + return nil, nil, nil, problemIn.CheckIfLinear() } // 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. + problemWithAllPositiveVariables, originalVariablesToPositiveVariableExpressions, err := problemIn.ToProblemWithAllPositiveVariables() // Note: This method may change the number of variables and constraints in the problem. if err != nil { - return nil, nil, err + return nil, nil, nil, err } // Create a new problem @@ -692,11 +692,11 @@ func (problemIn *OptimizationProblem) ToLPStandardForm1() (*OptimizationProblem, ) // Add all variables to the new problem - mapFromInToNewVariables := make(map[symbolic.Variable]symbolic.Expression) + mapFromPositiveToNewVariables := make(map[symbolic.Variable]symbolic.Expression) for _, varII := range problemWithAllPositiveVariables.Variables { problemInStandardForm.AddVariable() nVariables := len(problemInStandardForm.Variables) - mapFromInToNewVariables[varII] = problemInStandardForm.Variables[nVariables-1] + mapFromPositiveToNewVariables[varII] = problemInStandardForm.Variables[nVariables-1] } // Add all constraints to the new problem @@ -706,10 +706,10 @@ func (problemIn *OptimizationProblem) ToLPStandardForm1() (*OptimizationProblem, // Create a new expression by substituting the variables according // to the map we created above oldLHS := constraint.Left() - newLHS := oldLHS.SubstituteAccordingTo(mapFromInToNewVariables) + newLHS := oldLHS.SubstituteAccordingTo(mapFromPositiveToNewVariables) oldRHS := constraint.Right() - newRHS := oldRHS.SubstituteAccordingTo(mapFromInToNewVariables) + newRHS := oldRHS.SubstituteAccordingTo(mapFromPositiveToNewVariables) switch constraint.ConstrSense() { case symbolic.SenseEqual: @@ -742,7 +742,7 @@ func (problemIn *OptimizationProblem) ToLPStandardForm1() (*OptimizationProblem, newLHS = newLHS.Plus(problemInStandardForm.Variables[nVariables-1]) default: - return nil, nil, fmt.Errorf( + return nil, nil, nil, fmt.Errorf( "Unknown constraint sense: " + constraint.ConstrSense().String(), ) } @@ -762,7 +762,7 @@ func (problemIn *OptimizationProblem) ToLPStandardForm1() (*OptimizationProblem, // Now, let's create the new objective function by substituting the variables // according to the map we created above newObjectiveExpression := problemWithAllPositiveVariables.Objective.Expression.SubstituteAccordingTo( - mapFromInToNewVariables, + mapFromPositiveToNewVariables, ) problemInStandardForm.SetObjective( newObjectiveExpression, @@ -772,8 +772,16 @@ func (problemIn *OptimizationProblem) ToLPStandardForm1() (*OptimizationProblem, // Simplify The Constraints If Possible problemInStandardForm.SimplifyConstraints() + // Create the map from the original variables to the new variables + // by composing the two maps we created above + originalVariablesToNewVariables := make(map[symbolic.Variable]symbolic.Expression) + for originalVar, positiveVarExpr := range originalVariablesToPositiveVariableExpressions { + newVarExpr := positiveVarExpr.SubstituteAccordingTo(mapFromPositiveToNewVariables) + originalVariablesToNewVariables[originalVar] = newVarExpr + } + // Return the new problem and the slack variables - return problemInStandardForm, slackVariables, nil + return problemInStandardForm, slackVariables, originalVariablesToNewVariables, nil } /* @@ -795,17 +803,17 @@ Description: 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) { +func (problemIn *OptimizationProblem) ToLPStandardForm2() (*OptimizationProblem, []symbolic.Variable, map[symbolic.Variable]symbolic.Expression, error) { // Input Processing err := problemIn.Check() if err != nil { - return nil, nil, problemIn.MakeNotWellDefinedError() + return nil, nil, nil, problemIn.MakeNotWellDefinedError() } // Use the existing method to convert to standard form 1 - problemInStandardForm, slackVariables, err := problemIn.ToLPStandardForm1() + problemInStandardForm, slackVariables, originalVariablesToNewVariables, err := problemIn.ToLPStandardForm1() if err != nil { - return nil, nil, err + return nil, nil, nil, err } // Modify the objective function to be a maximization problem, @@ -816,12 +824,12 @@ func (problemIn *OptimizationProblem) ToLPStandardForm2() (*OptimizationProblem, 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 nil, nil, originalVariablesToNewVariables, 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 + return problemInStandardForm, slackVariables, originalVariablesToNewVariables, nil } /* From 24d38334f460b77a8a00e5a4d225f1ec11c2d25f Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Wed, 8 Oct 2025 00:59:45 -0400 Subject: [PATCH 3/6] Fixed all tests --- testing/problem/optimization_problem_test.go | 32 ++++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/testing/problem/optimization_problem_test.go b/testing/problem/optimization_problem_test.go index 4207b1c..0942a67 100644 --- a/testing/problem/optimization_problem_test.go +++ b/testing/problem/optimization_problem_test.go @@ -1960,7 +1960,7 @@ func TestOptimizationProblem_LinearEqualityConstraintMatrices7(t *testing.T) { p1 := problem.GetExampleProblem3() // Transform p1 into the standard form - p1Standard, _, err := p1.ToLPStandardForm1() + p1Standard, _, _, err := p1.ToLPStandardForm1() if err != nil { t.Errorf("unexpected error: %v", err) } @@ -2045,7 +2045,7 @@ func TestOptimizationProblem_LinearEqualityConstraintMatrices9(t *testing.T) { p1 := problem.GetExampleProblem4() // Transform p1 into the standard form - p1Standard, _, err := p1.ToLPStandardForm1() + p1Standard, _, _, err := p1.ToLPStandardForm1() if err != nil { t.Errorf("unexpected error: %v", err) } @@ -2098,7 +2098,7 @@ func TestOptimizationProblem_LinearEqualityConstraintMatrices10(t *testing.T) { p1 := problem.GetExampleProblem5() // Transform p1 into the standard form - p1Standard, slackVariables, err := p1.ToLPStandardForm1() + p1Standard, slackVariables, _, err := p1.ToLPStandardForm1() if err != nil { t.Errorf("unexpected error: %v", err) } @@ -2159,7 +2159,7 @@ func TestOptimizationProblem_ToProblemWithAllPositiveVariables1(t *testing.T) { ) // Algorithm - p2, err := p1.ToProblemWithAllPositiveVariables() + p2, _, err := p1.ToProblemWithAllPositiveVariables() if err != nil { t.Errorf("unexpected error: %v", err) } @@ -2213,7 +2213,7 @@ func TestOptimizationProblem_ToProblemWithAllPositiveVariables2(t *testing.T) { ) // Algorithm - p2, err := p1.ToProblemWithAllPositiveVariables() + p2, _, err := p1.ToProblemWithAllPositiveVariables() if err != nil { t.Errorf("unexpected error: %v", err) } @@ -2265,7 +2265,7 @@ func TestOptimizationProblem_ToLPStandardForm1_1(t *testing.T) { ) // Algorithm - p2, _, err := p1.ToLPStandardForm1() + p2, _, _, err := p1.ToLPStandardForm1() if err != nil { t.Errorf("unexpected error: %v", err) } @@ -2327,7 +2327,7 @@ func TestOptimizationProblem_ToLPStandardForm1_2(t *testing.T) { ) // Algorithm - p2, _, err := p1.ToLPStandardForm1() + p2, _, _, err := p1.ToLPStandardForm1() if err != nil { t.Errorf("unexpected error: %v", err) } @@ -2397,7 +2397,7 @@ func TestOptimizationProblem_ToLPStandardForm1_3(t *testing.T) { ) // Algorithm - p2, _, err := p1.ToLPStandardForm1() + p2, _, _, err := p1.ToLPStandardForm1() if err != nil { t.Errorf("unexpected error: %v", err) } @@ -2464,7 +2464,7 @@ func TestOptimizationProblem_ToLPStandardForm1_4(t *testing.T) { ) // Algorithm - _, _, err := p1.ToLPStandardForm1() + _, _, _, err := p1.ToLPStandardForm1() if err == nil { t.Errorf("expected an error; received nil") } else { @@ -2510,7 +2510,7 @@ func TestOptimizationProblem_ToLPStandardForm1_5(t *testing.T) { ) // Algorithm - _, _, err := p1.ToLPStandardForm1() + _, _, _, err := p1.ToLPStandardForm1() if err == nil { t.Errorf("expected an error; received nil") } else { @@ -2556,7 +2556,7 @@ func TestOptimizationProblem_ToLPStandardForm1_6(t *testing.T) { ) // Algorithm - p2, _, err := p1.ToLPStandardForm1() + p2, _, _, err := p1.ToLPStandardForm1() if err != nil { t.Errorf("unexpected error: %v", err) } @@ -2613,7 +2613,7 @@ func TestOptimizationProblem_ToLPStandardForm1_7(t *testing.T) { ) // Algorithm - p2, _, err := p1.ToLPStandardForm1() + p2, _, _, err := p1.ToLPStandardForm1() if err != nil { t.Errorf("unexpected error: %v", err) } @@ -2675,7 +2675,7 @@ func TestOptimizationProblem_ToLPStandardForm1_8(t *testing.T) { ) // Algorithm - p1Prime, _, err := p1.ToLPStandardForm1() + p1Prime, _, _, err := p1.ToLPStandardForm1() if err != nil { t.Errorf("unexpected error: %v", err) } @@ -2756,7 +2756,7 @@ func TestOptimizationProblem_ToLPStandardForm1_9(t *testing.T) { ) // Algorithm - p1Prime, _, err := p1.ToLPStandardForm1() + p1Prime, _, _, err := p1.ToLPStandardForm1() if err != nil { t.Errorf("unexpected error: %v", err) } @@ -2834,7 +2834,7 @@ func TestOptimizationProblem_ToLPStandardForm1_10(t *testing.T) { ) // Call the ToLPStandardForm1 - _, _, err := p1.ToLPStandardForm1() + _, _, _, err := p1.ToLPStandardForm1() if err == nil { t.Errorf("expected an error; received nil") } @@ -3081,7 +3081,7 @@ func TestOptimizationProblem_ToLPStandardForm2_1(t *testing.T) { ) // Algorithm - p2, _, err := p1.ToLPStandardForm2() + p2, _, _, err := p1.ToLPStandardForm2() if err != nil { t.Errorf("unexpected error: %v", err) } From 11b70cc774ac41832d01272f2f23adf5a3838246 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Wed, 8 Oct 2025 23:20:51 -0400 Subject: [PATCH 4/6] Introducing new test to check the mapping from original variables to positive variables --- testing/problem/optimization_problem_test.go | 56 ++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/testing/problem/optimization_problem_test.go b/testing/problem/optimization_problem_test.go index 0942a67..b020254 100644 --- a/testing/problem/optimization_problem_test.go +++ b/testing/problem/optimization_problem_test.go @@ -2846,6 +2846,62 @@ func TestOptimizationProblem_ToLPStandardForm1_10(t *testing.T) { } } +/* +TestOptimizationProblem_ToLPStandardForm1_11 +Description: + + This method verifies that the map from original variables to + standard form variables is correct for a small problem. + In this problem, we will have: + - a constant objective + - 1 variable, + - and a single linear inequality constraint. + The resulting map should contain 1 entry, mapping the original + variable to the positive half and negative half variables in the standard form. +*/ +func TestOptimizationProblem_ToLPStandardForm1_11(t *testing.T) { + // Constants + p1 := problem.NewProblem("TestOptimizationProblem_ToLPStandardForm1_11") + 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, + ) + + // Algorithm + _, _, varMap, err := p1.ToLPStandardForm1() + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + // Check that the number of entries in the map is as expected. + if len(varMap) != 1 { + t.Errorf("expected the number of entries in the map to be %v; received %v", + 1, len(varMap)) + } + + // Check that the entry in the map contains two variables. + v1Expr, ok := varMap[v1] + if !ok { + t.Errorf("expected the map to contain an entry for variable %v; it does not", v1) + } + + if len(v1Expr.Variables()) != 2 { + t.Errorf("expected the entry in the map to contain %v variables; received %v", + 2, len(v1Expr.Variables())) + } + + if len(symbolic.UniqueVars(v1Expr.Variables())) != 2 { + t.Errorf("expected the entry in the map to contain %v unique variables; received %v", + 2, len(symbolic.UniqueVars(v1Expr.Variables()))) + } +} + /* TestOptimizationProblem_CheckIfLinear1 Description: From 5055e983fcc6669e873dee75064c17723afbe0d0 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Wed, 8 Oct 2025 23:23:33 -0400 Subject: [PATCH 5/6] Added test for purely positive and purely negative variables getting transformed into simple expressions. --- testing/problem/optimization_problem_test.go | 69 ++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/testing/problem/optimization_problem_test.go b/testing/problem/optimization_problem_test.go index b020254..38bb036 100644 --- a/testing/problem/optimization_problem_test.go +++ b/testing/problem/optimization_problem_test.go @@ -2902,6 +2902,75 @@ func TestOptimizationProblem_ToLPStandardForm1_11(t *testing.T) { } } +/* +TestOptimizationProblem_ToLPStandardForm1_12 +Description: + + This method verifies that the map from original variables to + standard form variables is correct for a small problem. + In this problem, we will have: + - a constant objective + - 2 variables, + - and two linear inequality constraints. + One of the variables is purely positive, while the other is purely negative. + The resulting map should contain 2 entries, one mapping the purely positive + variable to itself, and the other mapping the purely negative variable to + the negative half variables in the standard form. +*/ +func TestOptimizationProblem_ToLPStandardForm1_12(t *testing.T) { + // Constants + p1 := problem.NewProblem("TestOptimizationProblem_ToLPStandardForm1_12") + vv1 := p1.AddVariableVector(2) + // Add constraints + c1 := vv1.AtVec(0).GreaterEq(1.0) + c2 := vv1.AtVec(1).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, + ) + + // Algorithm + _, _, varMap, err := p1.ToLPStandardForm1() + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + // Check that the number of entries in the map is as expected. + if len(varMap) != 2 { + t.Errorf("expected the number of entries in the map to be %v; received %v", + 2, len(varMap)) + } + + // Check that the entry in the map for the purely positive variable contains one variable. + v1 := vv1.AtVec(0).(symbolic.Variable) + v1Expr, ok := varMap[v1] + if !ok { + t.Errorf("expected the map to contain an entry for variable %v; it does not", v1) + } + + if len(v1Expr.Variables()) != 1 { + t.Errorf("expected the entry in the map to contain %v variables; received %v", + 1, len(v1Expr.Variables())) + } + + // Check that the entry in the map for the purely negative variable contains one variable. + v2 := vv1.AtVec(1).(symbolic.Variable) + v2Expr, ok := varMap[v2] + if !ok { + t.Errorf("expected the map to contain an entry for variable %v; it does not", v2) + } + + if len(v2Expr.Variables()) != 1 { + t.Errorf("expected the entry in the map to contain %v variables; received %v", + 1, len(v2Expr.Variables())) + } +} + /* TestOptimizationProblem_CheckIfLinear1 Description: From 675e3069ef2acb2132b3325abb6f048ebe07c4e6 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Wed, 8 Oct 2025 23:32:20 -0400 Subject: [PATCH 6/6] Hiding a test for the simplified output of one polynomial --- problem/optimization_problem.go | 2 +- testing/problem/optimization_problem_test.go | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/problem/optimization_problem.go b/problem/optimization_problem.go index df4d774..89bd6bf 100644 --- a/problem/optimization_problem.go +++ b/problem/optimization_problem.go @@ -620,7 +620,7 @@ func (op *OptimizationProblem) ToProblemWithAllPositiveVariables() (*Optimizatio } // Set the original variable to be the difference of the two new variables - mapFromOriginalVariablesToNewExpressions[xII] = expressionForReplacement + mapFromOriginalVariablesToNewExpressions[xII] = expressionForReplacement.AsSimplifiedExpression() } // Now, let's create the new constraints by replacing the variables in the diff --git a/testing/problem/optimization_problem_test.go b/testing/problem/optimization_problem_test.go index 38bb036..2c42d9c 100644 --- a/testing/problem/optimization_problem_test.go +++ b/testing/problem/optimization_problem_test.go @@ -2969,6 +2969,18 @@ func TestOptimizationProblem_ToLPStandardForm1_12(t *testing.T) { t.Errorf("expected the entry in the map to contain %v variables; received %v", 1, len(v2Expr.Variables())) } + + // TODO: Uncomment these checks once we have Polynomial's AsSimplifiedExpression method better tuned for polynomials. + // v2ExprAsMonomial, ok := v2Expr.(symbolic.Monomial) + // if !ok { + // t.Errorf("expected the entry in the map to be a Monomial; received %T", v2Expr) + // } + + // coeff2 := v2ExprAsMonomial.LinearCoeff() + // if coeff2.AtVec(0) != -1.0 { + // t.Errorf("expected the entry in the map to have coefficient %v; received %v", + // -1.0, coeff2.AtVec(0)) + // } } /*