diff --git a/problem/solution.go b/problem/solution.go deleted file mode 100644 index 79f7d2c..0000000 --- a/problem/solution.go +++ /dev/null @@ -1,117 +0,0 @@ -package problem - -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 -} - -type OptimizationStatus 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 -) - -/* -ToMessage -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) { - // Converts each of the statuses to a text message that is human readable. - switch os { - case OptimizationStatus_LOADED: - return "Model is loaded, but no solution information is available.", nil - case OptimizationStatus_OPTIMAL: - return "Model was solved to optimality (subject to tolerances), and an optimal solution is available.", nil - case OptimizationStatus_INFEASIBLE: - return "Model was proven to be infeasible.", nil - case OptimizationStatus_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: - 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: - 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: - 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: - 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: - return "Optimization terminated because the time expended exceeded the value specified in the TimeLimit parameter.", nil - case OptimizationStatus_SOLUTION_LIMIT: - return "Optimization terminated because the number of solutions found reached the value specified in the SolutionLimit parameter.", nil - case OptimizationStatus_INTERRUPTED: - return "Optimization was terminated by the user.", nil - case OptimizationStatus_NUMERIC: - return "Optimization was terminated due to unrecoverable numerical difficulties.", nil - case OptimizationStatus_SUBOPTIMAL: - return "Unable to satisfy optimality tolerances; a sub-optimal solution is available.", nil - case OptimizationStatus_INPROGRESS: - return "An asynchronous optimization call was made, but the associated optimization run is not yet complete.", nil - case OptimizationStatus_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: - 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) - } -} - -// 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/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/solution/solution.go b/solution/solution.go new file mode 100644 index 0000000..8dc5b42 --- /dev/null +++ b/solution/solution.go @@ -0,0 +1,39 @@ +package solution + +import ( + "fmt" + + solution_status "github.com/MatProGo-dev/MatProInterface.go/solution/status" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" +) + +const ( + tinyNum float64 = 0.01 +) + +// Solution stores the solution of an optimization problem and associated +// metadata +type Solution interface { + GetOptimalValue() float64 + GetValueMap() map[uint64]float64 + + // GetStatus + // + GetStatus() solution_status.SolutionStatus +} + +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/solution/status/status.go b/solution/status/status.go new file mode 100644 index 0000000..d75fc81 --- /dev/null +++ b/solution/status/status.go @@ -0,0 +1,72 @@ +package solution_status + +import "fmt" + +type SolutionStatus int + +// OptimizationStatuses +const ( + 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 +) + +/* +ToMessage +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 SolutionStatus) ToMessage() (string, error) { + // Converts each of the statuses to a text message that is human readable. + switch os { + case LOADED: + return "Model is loaded, but no solution information is available.", nil + case OPTIMAL: + return "Model was solved to optimality (subject to tolerances), and an optimal solution is available.", nil + case INFEASIBLE: + return "Model was proven to be infeasible.", nil + 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 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 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 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 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 TIME_LIMIT: + return "Optimization terminated because the time expended exceeded the value specified in the TimeLimit parameter.", nil + case SOLUTION_LIMIT: + return "Optimization terminated because the number of solutions found reached the value specified in the SolutionLimit parameter.", nil + case INTERRUPTED: + return "Optimization was terminated by the user.", nil + case NUMERIC: + return "Optimization was terminated due to unrecoverable numerical difficulties.", nil + case SUBOPTIMAL: + return "Unable to satisfy optimality tolerances; a sub-optimal solution is available.", nil + case INPROGRESS: + return "An asynchronous optimization call was made, but the associated optimization run is not yet complete.", nil + 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 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/problem/solution_test.go b/testing/solution/solution_test.go similarity index 80% rename from testing/problem/solution_test.go rename to testing/solution/solution_test.go index d7e254e..8a3da5e 100644 --- a/testing/problem/solution_test.go +++ b/testing/solution/solution_test.go @@ -1,10 +1,12 @@ -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" + solution_status "github.com/MatProGo-dev/MatProInterface.go/solution/status" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" ) /* @@ -16,13 +18,13 @@ Description: func TestSolution_ToMessage1(t *testing.T) { // Constants - tempSol := problem.Solution{ + tempSol := solution.DummySolution{ Values: map[uint64]float64{ 0: 2.1, 1: 3.14, }, Objective: 2.3, - Status: problem.OptimizationStatus_NODE_LIMIT, + Status: solution_status.NODE_LIMIT, } // Test the ToMessage() Call on this solution. @@ -45,7 +47,7 @@ func TestSolution_ToMessage2(t *testing.T) { // Test for statusIndex := 1; statusIndex < statusMax; statusIndex++ { - tempStatus := problem.OptimizationStatus(statusIndex) + tempStatus := solution_status.SolutionStatus(statusIndex) msg, err := tempStatus.ToMessage() if err != nil { @@ -94,33 +96,40 @@ Description: */ func TestSolution_Value1(t *testing.T) { // Constants - tempSol := problem.Solution{ + v1 := symbolic.NewVariable() + v2 := symbolic.NewVariable() + + tempSol := solution.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_status.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, ) }