From a97251ed349182f64061a0a215502d8568f30df6 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Fri, 3 Oct 2025 23:46:24 -0400 Subject: [PATCH 1/6] Moved some of the files out of the problem package for solutions --- solution/solution.go | 38 +++++++++ problem/solution.go => solution/status.go | 49 +----------- .../{problem => solution}/solution_test.go | 77 ++++++++++++++----- 3 files changed, 97 insertions(+), 67 deletions(-) create mode 100644 solution/solution.go rename problem/solution.go => solution/status.go (76%) rename testing/{problem => solution}/solution_test.go (71%) diff --git a/solution/solution.go b/solution/solution.go new file mode 100644 index 0000000..5ecf7c5 --- /dev/null +++ b/solution/solution.go @@ -0,0 +1,38 @@ +package solution + +import ( + "fmt" + + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" +) + +const ( + tinyNum float64 = 0.01 +) + +// Solution stores the solution of an optimization problem and associated +// metatdata +type Solution interface { + GetOptimalValue() float64 + GetValueMap() map[uint64]float64 + + // GetStatus + // + GetStatus() OptimizationStatus +} + +func ExtractValueOfVariableWithID(s Solution, idx uint64) (float64, error) { + val, ok := s.GetValueMap()[idx] + if !ok { + return 0.0, fmt.Errorf( + "The idx \"%v\" was not in the variable map for the solution.", + idx, + ) + } + return val, nil +} + +func ExtractValueOfVariable(s Solution, v symbolic.Variable) (float64, error) { + idx := v.ID // Extract index of v + return ExtractValueOfVariableWithID(s, idx) +} diff --git a/problem/solution.go b/solution/status.go similarity index 76% rename from problem/solution.go rename to solution/status.go index 79f7d2c..e880c1d 100644 --- a/problem/solution.go +++ b/solution/status.go @@ -1,30 +1,6 @@ -package problem +package solution -import ( - "fmt" - "github.com/MatProGo-dev/MatProInterface.go/optim" -) - -const ( - tinyNum float64 = 0.01 -) - -// Solution stores the solution of an optimization problem and associated -// metatdata -type Solution struct { - Values map[uint64]float64 - - // The objective for the solution - Objective float64 - - // Whether or not the solution is within the optimality threshold - Status OptimizationStatus - - // The optimality gap returned from the solver. For many solvers, this is - // the gap between the best possible solution with integer relaxation and - // the best integer solution found so far. - // Gap float64 -} +import "fmt" type OptimizationStatus int @@ -94,24 +70,3 @@ func (os OptimizationStatus) ToMessage() (string, error) { return "", fmt.Errorf("The status with value %v is unrecognized.", os) } } - -// func newSolution(mipSol solvers.MIPSolution) *Solution { -// return &Solution{ -// vals: mipSol.GetValues(), -// Objective: mipSol.GetObj(), -// Optimal: mipSol.GetOptimal(), -// Gap: mipSol.GetGap(), -// } -// } - -// Value returns the value assigned to the variable in the solution -func (s *Solution) Value(v optim.Variable) float64 { - return s.Values[v.ID] -} - -//// IsOne returns true if the value assigned to the variable is an integer, -//// and assigned to one. This is a convenience method which should not be -//// super trusted... -//func (s *Solution) IsOne(v Variable) bool { -// return (v.Vtype == Integer || v.Vtype == Binary) && s.Value(v) > tinyNum -//} diff --git a/testing/problem/solution_test.go b/testing/solution/solution_test.go similarity index 71% rename from testing/problem/solution_test.go rename to testing/solution/solution_test.go index d7e254e..2e5daa2 100644 --- a/testing/problem/solution_test.go +++ b/testing/solution/solution_test.go @@ -1,12 +1,42 @@ -package problem +package solution_test import ( - "github.com/MatProGo-dev/MatProInterface.go/optim" - "github.com/MatProGo-dev/MatProInterface.go/problem" "strings" "testing" + + "github.com/MatProGo-dev/MatProInterface.go/solution" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" ) +/* + */ +type DummySolution struct { + Values map[uint64]float64 + + // The objective for the solution + Objective float64 + + // Whether or not the solution is within the optimality threshold + Status solution.OptimizationStatus + + // The optimality gap returned from the solver. For many solvers, this is + // the gap between the best possible solution with integer relaxation and + // the best integer solution found so far. + // Gap float64 +} + +func (ds *DummySolution) GetOptimalValue() float64 { + return ds.Objective +} + +func (ds *DummySolution) GetValueMap() map[uint64]float64 { + return ds.Values +} + +func (ds *DummySolution) GetStatus() solution.OptimizationStatus { + return ds.Status +} + /* solution_test.go Description: @@ -16,13 +46,13 @@ Description: func TestSolution_ToMessage1(t *testing.T) { // Constants - tempSol := problem.Solution{ + tempSol := DummySolution{ Values: map[uint64]float64{ 0: 2.1, 1: 3.14, }, Objective: 2.3, - Status: problem.OptimizationStatus_NODE_LIMIT, + Status: solution.OptimizationStatus_NODE_LIMIT, } // Test the ToMessage() Call on this solution. @@ -45,7 +75,7 @@ func TestSolution_ToMessage2(t *testing.T) { // Test for statusIndex := 1; statusIndex < statusMax; statusIndex++ { - tempStatus := problem.OptimizationStatus(statusIndex) + tempStatus := solution.OptimizationStatus(statusIndex) msg, err := tempStatus.ToMessage() if err != nil { @@ -94,33 +124,40 @@ Description: */ func TestSolution_Value1(t *testing.T) { // Constants - tempSol := problem.Solution{ + v1 := symbolic.NewVariable() + v2 := symbolic.NewVariable() + + tempSol := DummySolution{ Values: map[uint64]float64{ - 0: 2.1, - 1: 3.14, + v1.ID: 2.1, + v2.ID: 3.14, }, Objective: 2.3, - Status: problem.OptimizationStatus_NODE_LIMIT, - } - v1 := optim.Variable{ - ID: 0, Lower: -optim.INFINITY, Upper: optim.INFINITY, Vtype: optim.Continuous, - } - v2 := optim.Variable{ - ID: 1, Lower: -optim.INFINITY, Upper: optim.INFINITY, Vtype: optim.Continuous, + Status: solution.OptimizationStatus_NODE_LIMIT, } // Algorithm - if tempSol.Value(v1) != 2.1 { + v1Val, err := solution.ExtractValueOfVariable(&tempSol, v1) + if err != nil { + t.Errorf("The value of the variable v1 could not be extracted; received error %v", err) + } + + if v1Val != 2.1 { t.Errorf( "Expected v1 to have value 2.1; received %v", - tempSol.Value(v1), + v1Val, ) } - if tempSol.Value(v2) != 3.14 { + v2Val, err := solution.ExtractValueOfVariable(&tempSol, v2) + if err != nil { + t.Errorf("the value of the variable v2 could not be extracted; received error %v", err) + } + + if v2Val != 3.14 { t.Errorf( "Expected v2 to have value 3.14; received %v", - tempSol.Value(v2), + v2Val, ) } From 7a7a52e72f911f6744a9d08f2e37c1e99b5f3f6d Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Fri, 3 Oct 2025 23:49:38 -0400 Subject: [PATCH 2/6] Moved solution status to its own package --- solution/{ => status}/status.go | 38 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) rename solution/{ => status}/status.go (75%) diff --git a/solution/status.go b/solution/status/status.go similarity index 75% rename from solution/status.go rename to solution/status/status.go index e880c1d..8e28d89 100644 --- a/solution/status.go +++ b/solution/status/status.go @@ -1,27 +1,27 @@ -package solution +package solution_status import "fmt" -type OptimizationStatus int +type SolutionStatus int // OptimizationStatuses const ( - OptimizationStatus_LOADED OptimizationStatus = 1 - OptimizationStatus_OPTIMAL = 2 - OptimizationStatus_INFEASIBLE = 3 - OptimizationStatus_INF_OR_UNBD = 4 - OptimizationStatus_UNBOUNDED = 5 - OptimizationStatus_CUTOFF = 6 - OptimizationStatus_ITERATION_LIMIT = 7 - OptimizationStatus_NODE_LIMIT = 8 - OptimizationStatus_TIME_LIMIT = 9 - OptimizationStatus_SOLUTION_LIMIT = 10 - OptimizationStatus_INTERRUPTED = 11 - OptimizationStatus_NUMERIC = 12 - OptimizationStatus_SUBOPTIMAL = 13 - OptimizationStatus_INPROGRESS = 14 - OptimizationStatus_USER_OBJ_LIMIT = 15 - OptimizationStatus_WORK_LIMIT = 16 + OptimizationStatus_LOADED SolutionStatus = 1 + OptimizationStatus_OPTIMAL = 2 + OptimizationStatus_INFEASIBLE = 3 + OptimizationStatus_INF_OR_UNBD = 4 + OptimizationStatus_UNBOUNDED = 5 + OptimizationStatus_CUTOFF = 6 + OptimizationStatus_ITERATION_LIMIT = 7 + OptimizationStatus_NODE_LIMIT = 8 + OptimizationStatus_TIME_LIMIT = 9 + OptimizationStatus_SOLUTION_LIMIT = 10 + OptimizationStatus_INTERRUPTED = 11 + OptimizationStatus_NUMERIC = 12 + OptimizationStatus_SUBOPTIMAL = 13 + OptimizationStatus_INPROGRESS = 14 + OptimizationStatus_USER_OBJ_LIMIT = 15 + OptimizationStatus_WORK_LIMIT = 16 ) /* @@ -31,7 +31,7 @@ Description: Translates the code to the text meaning. This comes from the status codes documentation: https://www.gurobi.com/documentation/9.5/refman/optimization_status_codes.html#sec:StatusCodes */ -func (os OptimizationStatus) ToMessage() (string, error) { +func (os SolutionStatus) ToMessage() (string, error) { // Converts each of the statuses to a text message that is human readable. switch os { case OptimizationStatus_LOADED: From 5daa48f027cd7d198ef4a11845bdf9cf856e00d2 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Fri, 3 Oct 2025 23:57:31 -0400 Subject: [PATCH 3/6] Refactoring out a bunch of the prefixes to SolutionStatus --- solution/solution.go | 3 +- solution/status/status.go | 64 +++++++++++++++---------------- testing/solution/solution_test.go | 11 +++--- 3 files changed, 40 insertions(+), 38 deletions(-) diff --git a/solution/solution.go b/solution/solution.go index 5ecf7c5..b0f62ba 100644 --- a/solution/solution.go +++ b/solution/solution.go @@ -3,6 +3,7 @@ package solution import ( "fmt" + solution_status "github.com/MatProGo-dev/MatProInterface.go/solution/status" "github.com/MatProGo-dev/SymbolicMath.go/symbolic" ) @@ -18,7 +19,7 @@ type Solution interface { // GetStatus // - GetStatus() OptimizationStatus + GetStatus() solution_status.SolutionStatus } func ExtractValueOfVariableWithID(s Solution, idx uint64) (float64, error) { diff --git a/solution/status/status.go b/solution/status/status.go index 8e28d89..d75fc81 100644 --- a/solution/status/status.go +++ b/solution/status/status.go @@ -6,22 +6,22 @@ type SolutionStatus int // OptimizationStatuses const ( - OptimizationStatus_LOADED SolutionStatus = 1 - OptimizationStatus_OPTIMAL = 2 - OptimizationStatus_INFEASIBLE = 3 - OptimizationStatus_INF_OR_UNBD = 4 - OptimizationStatus_UNBOUNDED = 5 - OptimizationStatus_CUTOFF = 6 - OptimizationStatus_ITERATION_LIMIT = 7 - OptimizationStatus_NODE_LIMIT = 8 - OptimizationStatus_TIME_LIMIT = 9 - OptimizationStatus_SOLUTION_LIMIT = 10 - OptimizationStatus_INTERRUPTED = 11 - OptimizationStatus_NUMERIC = 12 - OptimizationStatus_SUBOPTIMAL = 13 - OptimizationStatus_INPROGRESS = 14 - OptimizationStatus_USER_OBJ_LIMIT = 15 - OptimizationStatus_WORK_LIMIT = 16 + LOADED SolutionStatus = 1 + OPTIMAL SolutionStatus = 2 + INFEASIBLE SolutionStatus = 3 + INF_OR_UNBD SolutionStatus = 4 + UNBOUNDED SolutionStatus = 5 + CUTOFF SolutionStatus = 6 + ITERATION_LIMIT SolutionStatus = 7 + NODE_LIMIT SolutionStatus = 8 + TIME_LIMIT SolutionStatus = 9 + SOLUTION_LIMIT SolutionStatus = 10 + INTERRUPTED SolutionStatus = 11 + NUMERIC SolutionStatus = 12 + SUBOPTIMAL SolutionStatus = 13 + INPROGRESS SolutionStatus = 14 + USER_OBJ_LIMIT SolutionStatus = 15 + WORK_LIMIT SolutionStatus = 16 ) /* @@ -34,37 +34,37 @@ Description: func (os SolutionStatus) ToMessage() (string, error) { // Converts each of the statuses to a text message that is human readable. switch os { - case OptimizationStatus_LOADED: + case LOADED: return "Model is loaded, but no solution information is available.", nil - case OptimizationStatus_OPTIMAL: + case OPTIMAL: return "Model was solved to optimality (subject to tolerances), and an optimal solution is available.", nil - case OptimizationStatus_INFEASIBLE: + case INFEASIBLE: return "Model was proven to be infeasible.", nil - case OptimizationStatus_INF_OR_UNBD: + case INF_OR_UNBD: return "Model was proven to be either infeasible or unbounded. To obtain a more definitive conclusion, set the DualReductions parameter to 0 and reoptimize.", nil - case OptimizationStatus_UNBOUNDED: + case UNBOUNDED: return "Model was proven to be unbounded. Important note: an unbounded status indicates the presence of an unbounded ray that allows the objective to improve without limit. It says nothing about whether the model has a feasible solution. If you require information on feasibility, you should set the objective to zero and reoptimize.", nil - case OptimizationStatus_CUTOFF: + case CUTOFF: return "Optimal objective for model was proven to be worse than the value specified in the Cutoff parameter. No solution information is available.", nil - case OptimizationStatus_ITERATION_LIMIT: + case ITERATION_LIMIT: return "Optimization terminated because the total number of simplex iterations performed exceeded the value specified in the IterationLimit parameter, or because the total number of barrier iterations exceeded the value specified in the BarIterLimit parameter.", nil - case OptimizationStatus_NODE_LIMIT: + case NODE_LIMIT: return "Optimization terminated because the total number of branch-and-cut nodes explored exceeded the value specified in the NodeLimit parameter.", nil - case OptimizationStatus_TIME_LIMIT: + case TIME_LIMIT: return "Optimization terminated because the time expended exceeded the value specified in the TimeLimit parameter.", nil - case OptimizationStatus_SOLUTION_LIMIT: + case SOLUTION_LIMIT: return "Optimization terminated because the number of solutions found reached the value specified in the SolutionLimit parameter.", nil - case OptimizationStatus_INTERRUPTED: + case INTERRUPTED: return "Optimization was terminated by the user.", nil - case OptimizationStatus_NUMERIC: + case NUMERIC: return "Optimization was terminated due to unrecoverable numerical difficulties.", nil - case OptimizationStatus_SUBOPTIMAL: + case SUBOPTIMAL: return "Unable to satisfy optimality tolerances; a sub-optimal solution is available.", nil - case OptimizationStatus_INPROGRESS: + case INPROGRESS: return "An asynchronous optimization call was made, but the associated optimization run is not yet complete.", nil - case OptimizationStatus_USER_OBJ_LIMIT: + case USER_OBJ_LIMIT: return "User specified an objective limit (a bound on either the best objective or the best bound), and that limit has been reached.", nil - case OptimizationStatus_WORK_LIMIT: + case WORK_LIMIT: return "Optimization terminated because the work expended exceeded the value specified in the WorkLimit parameter.", nil default: return "", fmt.Errorf("The status with value %v is unrecognized.", os) diff --git a/testing/solution/solution_test.go b/testing/solution/solution_test.go index 2e5daa2..44e1637 100644 --- a/testing/solution/solution_test.go +++ b/testing/solution/solution_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/MatProGo-dev/MatProInterface.go/solution" + solution_status "github.com/MatProGo-dev/MatProInterface.go/solution/status" "github.com/MatProGo-dev/SymbolicMath.go/symbolic" ) @@ -17,7 +18,7 @@ type DummySolution struct { Objective float64 // Whether or not the solution is within the optimality threshold - Status solution.OptimizationStatus + Status solution_status.SolutionStatus // The optimality gap returned from the solver. For many solvers, this is // the gap between the best possible solution with integer relaxation and @@ -33,7 +34,7 @@ func (ds *DummySolution) GetValueMap() map[uint64]float64 { return ds.Values } -func (ds *DummySolution) GetStatus() solution.OptimizationStatus { +func (ds *DummySolution) GetStatus() solution_status.SolutionStatus { return ds.Status } @@ -52,7 +53,7 @@ func TestSolution_ToMessage1(t *testing.T) { 1: 3.14, }, Objective: 2.3, - Status: solution.OptimizationStatus_NODE_LIMIT, + Status: solution_status.NODE_LIMIT, } // Test the ToMessage() Call on this solution. @@ -75,7 +76,7 @@ func TestSolution_ToMessage2(t *testing.T) { // Test for statusIndex := 1; statusIndex < statusMax; statusIndex++ { - tempStatus := solution.OptimizationStatus(statusIndex) + tempStatus := solution_status.SolutionStatus(statusIndex) msg, err := tempStatus.ToMessage() if err != nil { @@ -133,7 +134,7 @@ func TestSolution_Value1(t *testing.T) { v2.ID: 3.14, }, Objective: 2.3, - Status: solution.OptimizationStatus_NODE_LIMIT, + Status: solution_status.NODE_LIMIT, } // Algorithm From 000cbd65d51fbf8a7b635d6546a2ed6f594401c1 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Sat, 4 Oct 2025 11:47:09 -0400 Subject: [PATCH 4/6] Separated dummy solution --- solution/dummy_solution.go | 30 ++++++++++++++++++++++++++++ testing/solution/solution_test.go | 33 ++----------------------------- 2 files changed, 32 insertions(+), 31 deletions(-) create mode 100644 solution/dummy_solution.go diff --git a/solution/dummy_solution.go b/solution/dummy_solution.go new file mode 100644 index 0000000..1d6dbe6 --- /dev/null +++ b/solution/dummy_solution.go @@ -0,0 +1,30 @@ +package solution + +import solution_status "github.com/MatProGo-dev/MatProInterface.go/solution/status" + +type DummySolution struct { + Values map[uint64]float64 + + // The objective for the solution + Objective float64 + + // Whether or not the solution is within the optimality threshold + Status solution_status.SolutionStatus + + // The optimality gap returned from the solver. For many solvers, this is + // the gap between the best possible solution with integer relaxation and + // the best integer solution found so far. + // Gap float64 +} + +func (ds *DummySolution) GetOptimalValue() float64 { + return ds.Objective +} + +func (ds *DummySolution) GetValueMap() map[uint64]float64 { + return ds.Values +} + +func (ds *DummySolution) GetStatus() solution_status.SolutionStatus { + return ds.Status +} diff --git a/testing/solution/solution_test.go b/testing/solution/solution_test.go index 44e1637..50cbea0 100644 --- a/testing/solution/solution_test.go +++ b/testing/solution/solution_test.go @@ -9,35 +9,6 @@ import ( "github.com/MatProGo-dev/SymbolicMath.go/symbolic" ) -/* - */ -type DummySolution struct { - Values map[uint64]float64 - - // The objective for the solution - Objective float64 - - // Whether or not the solution is within the optimality threshold - Status solution_status.SolutionStatus - - // The optimality gap returned from the solver. For many solvers, this is - // the gap between the best possible solution with integer relaxation and - // the best integer solution found so far. - // Gap float64 -} - -func (ds *DummySolution) GetOptimalValue() float64 { - return ds.Objective -} - -func (ds *DummySolution) GetValueMap() map[uint64]float64 { - return ds.Values -} - -func (ds *DummySolution) GetStatus() solution_status.SolutionStatus { - return ds.Status -} - /* solution_test.go Description: @@ -47,7 +18,7 @@ Description: func TestSolution_ToMessage1(t *testing.T) { // Constants - tempSol := DummySolution{ + tempSol := solution.DummySolution{ Values: map[uint64]float64{ 0: 2.1, 1: 3.14, @@ -128,7 +99,7 @@ func TestSolution_Value1(t *testing.T) { v1 := symbolic.NewVariable() v2 := symbolic.NewVariable() - tempSol := DummySolution{ + tempSol := solution.DummySolution{ Values: map[uint64]float64{ v1.ID: 2.1, v2.ID: 3.14, From 3ddedf7381d44e607d51cc512bb71249c39c6766 Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Sat, 4 Oct 2025 13:40:41 -0400 Subject: [PATCH 5/6] Update solution/solution.go fixing comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- solution/solution.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solution/solution.go b/solution/solution.go index b0f62ba..8dc5b42 100644 --- a/solution/solution.go +++ b/solution/solution.go @@ -12,7 +12,7 @@ const ( ) // Solution stores the solution of an optimization problem and associated -// metatdata +// metadata type Solution interface { GetOptimalValue() float64 GetValueMap() map[uint64]float64 From 056ed7c18f1682404c817d4c3294b70497358f1f Mon Sep 17 00:00:00 2001 From: Kwesi Rutledge Date: Sat, 4 Oct 2025 13:41:06 -0400 Subject: [PATCH 6/6] Update testing/solution/solution_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- testing/solution/solution_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/solution/solution_test.go b/testing/solution/solution_test.go index 50cbea0..8a3da5e 100644 --- a/testing/solution/solution_test.go +++ b/testing/solution/solution_test.go @@ -123,7 +123,7 @@ func TestSolution_Value1(t *testing.T) { v2Val, err := solution.ExtractValueOfVariable(&tempSol, v2) if err != nil { - t.Errorf("the value of the variable v2 could not be extracted; received error %v", err) + t.Errorf("The value of the variable v2 could not be extracted; received error %v", err) } if v2Val != 3.14 {