diff --git a/README.md b/README.md index 2b17da4..264dec3 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,82 @@ # simplex A small library used to demonstrate the concepts of the Simplex algorithm in convex optimization. + +# Installation + +You can add this module to your package using: +```bash +go get github.com/MatProGo-dev/simplex +``` + +# Usage + +``` +package main + +import ( + "github.com/MatProGo-dev/MatProInterface.go/problem" + getKVector "github.com/MatProGo-dev/SymbolicMath.go/get/KVector" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" + "github.com/MatProGo-dev/simplex/simplexSolver" +) + +func BuildOptimizationProblem() problem.OptimizationProblem { + // setup + varCount := 2 + out := problem.NewProblem("Box LP Problem") + + // Create the variables + x := out.AddVariableVector(varCount) + + // Create the objective + c := getKVector.From( + []float64{1, 2}, + ) + out.SetObjective( + c.Transpose().Multiply(x), + problem.SenseMinimize, + ) + + // Create the constraints + // - x >= 0 + out.Constraints = append( + out.Constraints, + x.GreaterEq(symbolic.ZerosVector(varCount)), + ) + + // - x <= 1 + out.Constraints = append( + out.Constraints, + x.LessEq(symbolic.OnesVector(varCount)), + ) + + return *out +} + +func main() { + // This is just a placeholder to make the package "main" valid. + trickyProblem := BuildOptimizationProblem() + + // Use solver to solve the problem + solver := simplexSolver.New("Simplex Solver Example") + solver.IterationLimit = 100 + + // Solve the problem + solution, err := solver.Solve(trickyProblem) + if err != nil { + panic(err) + } + + // Print the solution + solutionMessage, _ := solution.Status.ToMessage() + println("Solution Status: ", solutionMessage) + println("Objective Value: ", solution.Objective) + println("Number of Iterations: ", solution.Iterations) + println("Variable Values: ") + for varName, varValue := range solution.VariableValues { + println(" ", varName, ": ", varValue) + } +} +``` + +See the examples directory for more example use cases for the library. \ No newline at end of file diff --git a/algorithms/algorithm_interface.go b/algorithms/algorithm_interface.go index 46625bb..64a3e96 100644 --- a/algorithms/algorithm_interface.go +++ b/algorithms/algorithm_interface.go @@ -2,9 +2,10 @@ package algorithms import ( "github.com/MatProGo-dev/MatProInterface.go/problem" + simplex_solution "github.com/MatProGo-dev/simplex/solution" ) type AlgorithmInterface interface { // Solves the provided optimization problem. - Solve(prob problem.OptimizationProblem) (problem.Solution, error) + Solve(prob problem.OptimizationProblem) (simplex_solution.SimplexSolution, error) } diff --git a/algorithms/stanford/stanford.go b/algorithms/stanford/stanford.go index badaabe..984cd5d 100644 --- a/algorithms/stanford/stanford.go +++ b/algorithms/stanford/stanford.go @@ -4,7 +4,9 @@ import ( "fmt" "github.com/MatProGo-dev/MatProInterface.go/problem" + solution_status "github.com/MatProGo-dev/MatProInterface.go/solution/status" "github.com/MatProGo-dev/SymbolicMath.go/symbolic" + simplex_solution "github.com/MatProGo-dev/simplex/solution" "github.com/MatProGo-dev/simplex/utils" "gonum.org/v1/gonum/mat" ) @@ -178,11 +180,11 @@ Description: - The values of the basic variables - The values of the non-basic variables */ -func (algo *StanfordAlgorithm) ComputeSolutionFromState(state StanfordAlgorithmState) (problem.Solution, error) { +func (algo *StanfordAlgorithm) ComputeSolutionFromState(state StanfordAlgorithmState) (simplex_solution.SimplexSolution, error) { // Setup fmt.Printf("Computing solution from state...\n") - solution := problem.Solution{ - Status: problem.OptimizationStatus_OPTIMAL, + solution := simplex_solution.SimplexSolution{ + Status: solution_status.OPTIMAL, } // Compute the feasible solution of the basic variables @@ -192,26 +194,25 @@ func (algo *StanfordAlgorithm) ComputeSolutionFromState(state StanfordAlgorithmS } // Compute the value of the objective function - objValue, err := algo.ComputeObjectiveFunctionValueWithFeasibleBasicSolution(state, xBasic) + _, err = algo.ComputeObjectiveFunctionValueWithFeasibleBasicSolution(state, xBasic) if err != nil { return solution, fmt.Errorf("StanfordAlgorithm: Failed to compute objective function value (%v)", err) } - solution.Objective = objValue // Set the values of the basic variables for ii, bv := range state.BasicVariables { - solution.Values[bv.ID] = xBasic.AtVec(ii) + solution.VariableValues[bv.ID] = xBasic.AtVec(ii) } // Set the values of the non-basic variables for jj, nv := range state.GetNonBasicVariables() { - solution.Values[nv.ID] = state.NonBasicValues.AtVec(jj) + solution.VariableValues[nv.ID] = state.NonBasicValues.AtVec(jj) } return solution, nil } -func (algo *StanfordAlgorithm) Solve(initialState StanfordAlgorithmState) (problem.Solution, error) { +func (algo *StanfordAlgorithm) Solve(initialState StanfordAlgorithmState) (simplex_solution.SimplexSolution, error) { // Setup var stateII StanfordAlgorithmState = initialState @@ -221,7 +222,7 @@ func (algo *StanfordAlgorithm) Solve(initialState StanfordAlgorithmState) (probl // Test for Termination r, err := stateII.GetReducedCostVector() if err != nil { - return problem.Solution{}, fmt.Errorf( + return simplex_solution.SimplexSolution{}, fmt.Errorf( "StanfordAlgorithm: Failed to get reduced cost vector (%v) at iteration #%v", err, iter, @@ -248,7 +249,7 @@ func (algo *StanfordAlgorithm) Solve(initialState StanfordAlgorithmState) (probl var ABasicInv mat.Dense ABasic, err := stateII.ABasic() if err != nil { - return problem.Solution{}, fmt.Errorf("StanfordAlgorithm: Failed to get ABasic matrix (%v)", err) + return simplex_solution.SimplexSolution{}, fmt.Errorf("StanfordAlgorithm: Failed to get ABasic matrix (%v)", err) } ABasicInv.Inverse(ABasic) var ABasicAe mat.VecDense @@ -263,7 +264,7 @@ func (algo *StanfordAlgorithm) Solve(initialState StanfordAlgorithmState) (probl } } if objectiveIsUnboundedBelow { - return problem.Solution{}, fmt.Errorf( + return simplex_solution.SimplexSolution{}, fmt.Errorf( "StanfordAlgorithm: Objective function is unbounded below, no solution exists at iteration %d", iter, ) @@ -274,13 +275,13 @@ func (algo *StanfordAlgorithm) Solve(initialState StanfordAlgorithmState) (probl // Compute the feasible Solution of the Basic variables xBasicII, err := algo.ComputeFeasibleBasicSolution(stateII) if err != nil { - return problem.Solution{}, err + return simplex_solution.SimplexSolution{}, err } // Compute the value of the objective function objII, err := algo.ComputeObjectiveFunctionValueWithFeasibleBasicSolution(stateII, xBasicII) if err != nil { - return problem.Solution{}, err + return simplex_solution.SimplexSolution{}, err } fmt.Printf("Iteration %d: Basic Solution: %v, Objective Value: %f\n", iter, xBasicII, objII) @@ -290,13 +291,12 @@ func (algo *StanfordAlgorithm) Solve(initialState StanfordAlgorithmState) (probl // Check if the solution is optimal if iter == algo.IterationLimit { - return problem.Solution{ - Objective: objII, - Status: problem.OptimizationStatus_ITERATION_LIMIT, + return simplex_solution.SimplexSolution{ + Status: solution_status.ITERATION_LIMIT, }, nil } } - return problem.Solution{}, nil + return simplex_solution.SimplexSolution{}, nil } diff --git a/algorithms/tableau/errors.go b/algorithms/tableau/errors.go new file mode 100644 index 0000000..ff38335 --- /dev/null +++ b/algorithms/tableau/errors.go @@ -0,0 +1,18 @@ +package tableau_algorithm1 + +type VariableSelectionError struct { + EnteringVarIndex int + ExitingVarIndex int +} + +func (e VariableSelectionError) Error() string { + if e.ExitingVarIndex == -1 { + return "VariableSelectionError: No exiting variable found, problem can not be improved." + } + + if e.EnteringVarIndex == -1 { + return "VariableSelectionError: No entering variable found, current solution is optimal." + } + + return "VariableSelectionError: Unknown variable selection error." +} diff --git a/algorithms/tableau/selection/blands_rule.go b/algorithms/tableau/selection/blands_rule.go index 8cf3821..f0e4670 100644 --- a/algorithms/tableau/selection/blands_rule.go +++ b/algorithms/tableau/selection/blands_rule.go @@ -2,7 +2,9 @@ package selection import ( "fmt" + "math" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "github.com/MatProGo-dev/simplex/utils" ) @@ -55,7 +57,7 @@ Description: func (br BlandsRule) SelectExitingVariable(tableau utils.Tableau, enteringVarIdx int) int { // Setup minIndex := -1 - minRatio := -1.0 + minRatio := float64(symbolic.Infinity) // Get the relevant matrices A := tableau.A() @@ -64,7 +66,7 @@ func (br BlandsRule) SelectExitingVariable(tableau utils.Tableau, enteringVarIdx // Create the vector of ratios ratios := make([]float64, tableau.NumberOfConstraints()) for i := 0; i < tableau.NumberOfConstraints(); i++ { - if A.At(i, enteringVarIdx) > 0 { + if math.Abs(A.At(i, enteringVarIdx)) > 1e-12 { ratios[i] = b.AtVec(i) / A.At(i, enteringVarIdx) } else { ratios[i] = -1.0 // Indicate that this variable cannot be used @@ -74,7 +76,7 @@ func (br BlandsRule) SelectExitingVariable(tableau utils.Tableau, enteringVarIdx // Iterate through the ratios and find the smallest one for i, ratio := range ratios { if ratio >= 0 { // Only consider valid ratios - if minRatio < 0 || ratio < minRatio || (ratio == minRatio && tableau.BasicVariableIndicies[i] < tableau.BasicVariableIndicies[minIndex]) { + if ratio < minRatio || (ratio == minRatio && tableau.BasicVariableIndicies[i] < tableau.BasicVariableIndicies[minIndex]) { minRatio = ratio minIndex = i } @@ -98,10 +100,12 @@ func (br BlandsRule) SelectEnteringAndExitingVariables(tableau utils.Tableau) (i return -1, -1, nil // Optimal solution found, no entering variable } + fmt.Println("Entering variable index:", enteringVarIdx) + // Select the exiting variable exitingVarIdx := br.SelectExitingVariable(tableau, enteringVarIdx) if exitingVarIdx == -1 { - return enteringVarIdx, -1, fmt.Errorf("BlandsRule: No exiting variable found, problem is unbounded (?)") + return enteringVarIdx, -1, fmt.Errorf("BlandsRule: No exiting variable found, problem can not be improved.") } return enteringVarIdx, exitingVarIdx, nil diff --git a/algorithms/tableau/state.go b/algorithms/tableau/state.go index 84103cd..ae187ab 100644 --- a/algorithms/tableau/state.go +++ b/algorithms/tableau/state.go @@ -7,6 +7,8 @@ import ( "github.com/MatProGo-dev/SymbolicMath.go/symbolic" "github.com/MatProGo-dev/simplex/algorithms" "github.com/MatProGo-dev/simplex/algorithms/tableau/selection" + tableau_termination "github.com/MatProGo-dev/simplex/algorithms/tableau/termination" + simplex_solution "github.com/MatProGo-dev/simplex/solution" "github.com/MatProGo-dev/simplex/utils" "gonum.org/v1/gonum/mat" ) @@ -90,17 +92,6 @@ func (state *TableauAlgorithmState) CheckTerminationCondition() (bool, error) { return true, nil } -func (state *TableauAlgorithmState) CurrentObjectiveValue() (float64, error) { - // Input Checking - err := state.Check() - if err != nil { - return 0.0, err - } - - // Setup - return state.Tableau.D(), nil -} - /* GetBasicVariables Description: @@ -250,15 +241,20 @@ func (state *TableauAlgorithmState) CalculateNextState() (TableauAlgorithmState, return TableauAlgorithmState{}, err } + // fmt.Println("Calculating next state from tableau:", mat.Formatted(state.Tableau.AsCompressedMatrix)) + // Select the pivot column and row (i.e., the entering and exiting variables in the tableau) // Here, we use Bland's Rule to select the entering variable // TODO(Kwesi): Make other rules available selectionRule := selection.BlandsRule{} enteringVarIdx, exitingVarIdx, err := selectionRule.SelectEnteringAndExitingVariables(*state.Tableau) if err != nil { - return TableauAlgorithmState{}, fmt.Errorf("TableauAlgorithmState: Failed to select entering and exiting variables (%v)", err) + return TableauAlgorithmState{}, VariableSelectionError{EnteringVarIndex: enteringVarIdx, ExitingVarIndex: exitingVarIdx} } + // fmt.Println("Entering variable index: ", enteringVarIdx, " (", state.Tableau.Variables[enteringVarIdx], ")") + // fmt.Println("Exiting variable index: ", exitingVarIdx, " (", state.Tableau.Variables[exitingVarIdx], ")") + // Create the new tableau newTab, err := state.Tableau.Pivot(enteringVarIdx, exitingVarIdx) if err != nil { @@ -288,7 +284,7 @@ func (state *TableauAlgorithmState) CalculateOptimalSolution() (mat.VecDense, er // - The non-basic variables set to zero A, b := state.A(), state.B() numConstraints, _ := A.Dims() - numNonBasic := state.NumberOfVariables() - state.NumberOfConstraints() + numNonBasic := state.Tableau.NumberOfNonBasicVariables() // Augment the A and b matrices with the non-basic variable constraints AAugmented := mat.NewDense(numConstraints+numNonBasic, numVars, nil) @@ -296,16 +292,14 @@ func (state *TableauAlgorithmState) CalculateOptimalSolution() (mat.VecDense, er bAugmented := mat.NewVecDense(numConstraints+numNonBasic, nil) bAugmented.CopyVec(b) - fmt.Println("A: ", mat.Formatted(AAugmented)) - // Add the non-basic variable constraints nonBasicVars := state.GetNonBasicVariables() for ii, v := range nonBasicVars { - fmt.Println("Adding non-basic variable constraint for variable: ", v) + // fmt.Println("Adding non-basic variable constraint for variable: ", v) // Find the index of the variable in the tableau vIdxInTableau, _ := symbolic.FindInSlice(v, state.Tableau.Variables) - fmt.Println("vIdxInTableau: ", vIdxInTableau) - fmt.Println("Targeted row: ", numConstraints+ii) + // fmt.Println("vIdxInTableau: ", vIdxInTableau) + // fmt.Println("Targeted row: ", numConstraints+ii) AAugmented.Set(numConstraints+ii, vIdxInTableau, 1.0) } // b is already zero in the new rows, so we don't need to set anything in bAugmented @@ -319,7 +313,7 @@ func (state *TableauAlgorithmState) CalculateOptimalSolution() (mat.VecDense, er return *solutionVec, nil } -func (state *TableauAlgorithmState) CreateOptimalValuesMap() (map[uint64]float64, error) { +func (state *TableauAlgorithmState) CreateOptimalValuesMap(originalVariablesAsStandardFormExpressions map[symbolic.Variable]symbolic.Expression) (map[uint64]float64, error) { // Input Checking err := state.Check() if err != nil { @@ -332,22 +326,56 @@ func (state *TableauAlgorithmState) CreateOptimalValuesMap() (map[uint64]float64 return nil, err } - // Create the map - solutionMap := map[uint64]float64{} + // Create the map between the STANDARD FORM variables and the optimal values + standardFormOptimalValues := map[symbolic.Variable]symbolic.Expression{} for ii, v := range state.Tableau.Variables { - solutionMap[v.ID] = solutionVec.AtVec(ii) + standardFormOptimalValues[v] = symbolic.K(solutionVec.AtVec(ii)) } - return solutionMap, nil + // Create the map between the ORIGINAL variables and the optimal values + optimalValueMap := map[uint64]float64{} + for origVar, expr := range originalVariablesAsStandardFormExpressions { + // Evaluate the expression (which is equal to the original variable) using the solution map + value := expr.SubstituteAccordingTo(standardFormOptimalValues) + valAsK, ok := value.(symbolic.K) + if !ok { + return nil, fmt.Errorf("TableauAlgorithmState: Failed to evaluate optimal value for original variable %v", origVar) + } + // Add the value to the output map + optimalValueMap[origVar.ID] = float64(valAsK) + } + + return optimalValueMap, nil } -func (state *TableauAlgorithmState) ToSolution(currentStatus problem.OptimizationStatus) problem.Solution { - // Construct Solution Map +func (state *TableauAlgorithmState) ToSolution( + condition tableau_termination.TerminationType, + varMap map[symbolic.Variable]symbolic.Expression, + originalProblem *problem.OptimizationProblem, +) (simplex_solution.SimplexSolution, error) { + // Create container for solution + var sol simplex_solution.SimplexSolution + var err error - // Assemble Solution Output - return problem.Solution{ - Values: map[uint64]float64{}, - Objective: -1.0, - Status: currentStatus, + // Construct Solution Status + sol.Status = condition.ToOptimizationStatus() + + // Construct Iteration Count + sol.Iterations = state.IterationCount + + // Attach original problem + sol.OriginalProblem = originalProblem + + // Construct Variable map + sol.VariableValues, err = state.CreateOptimalValuesMap(varMap) + if err != nil { + return sol, + fmt.Errorf( + "There was an issue creating the optimal values map at termination: %v", + err, + ) } + + // Assemble Solution Output + return sol, nil } diff --git a/algorithms/tableau/tableau_algo.go b/algorithms/tableau/tableau_algo.go index a2c25a6..fbd472c 100644 --- a/algorithms/tableau/tableau_algo.go +++ b/algorithms/tableau/tableau_algo.go @@ -5,6 +5,7 @@ import ( "github.com/MatProGo-dev/MatProInterface.go/problem" tableau_termination "github.com/MatProGo-dev/simplex/algorithms/tableau/termination" + simplex_solution "github.com/MatProGo-dev/simplex/solution" "github.com/MatProGo-dev/simplex/utils" "gonum.org/v1/gonum/mat" ) @@ -33,13 +34,13 @@ func (algo *TableauAlgorithm) CheckTerminationConditions(state TableauAlgorithmS return tableau_termination.DidNotTerminate, nil } -func (algo *TableauAlgorithm) Solve(prob problem.OptimizationProblem) (problem.Solution, error) { +func (algo *TableauAlgorithm) Solve(prob problem.OptimizationProblem) (simplex_solution.SimplexSolution, error) { // Setup // Create initial Tableau state from the problem - initialTableau, err := utils.GetInitialTableauFrom(&prob) + initialTableau, mapFromOriginalVariablesToStandardFormVariables, err := utils.GetInitialTableauFrom(&prob) if err != nil { - return problem.Solution{}, fmt.Errorf("there was an issue creating the initial tableau: %v", err) + return simplex_solution.SimplexSolution{}, fmt.Errorf("there was an issue creating the initial tableau: %v", err) } stateII := TableauAlgorithmState{ Tableau: &initialTableau, @@ -47,12 +48,12 @@ func (algo *TableauAlgorithm) Solve(prob problem.OptimizationProblem) (problem.S } // Loop - sol := problem.Solution{} + sol := simplex_solution.SimplexSolution{} for iter := 0; iter < algo.IterationLimit; iter++ { // Test for Termination condition, err := algo.CheckTerminationConditions(stateII) if err != nil { - return problem.Solution{}, + return simplex_solution.SimplexSolution{}, fmt.Errorf( "There was an issue checking the termination condition at iteration %v: %v", iter, @@ -61,24 +62,15 @@ func (algo *TableauAlgorithm) Solve(prob problem.OptimizationProblem) (problem.S } if condition != tableau_termination.DidNotTerminate { - sol.Status = condition.ToOptimizationStatus() - sol.Objective, err = stateII.CurrentObjectiveValue() + sol, err = stateII.ToSolution(condition, mapFromOriginalVariablesToStandardFormVariables, &prob) if err != nil { - return sol, + return simplex_solution.SimplexSolution{}, fmt.Errorf( - "There was an issue getting the objective value at termination: %v", + "There was an issue converting the final state to a solution at iteration %v: %v", + iter, err, ) } - sol.Values, err = stateII.CreateOptimalValuesMap() - if err != nil { - return sol, - fmt.Errorf( - "There was an issue creating the optimal values map at termination: %v", - err, - ) - } - // Exit the loop break } @@ -89,7 +81,7 @@ func (algo *TableauAlgorithm) Solve(prob problem.OptimizationProblem) (problem.S // Update the state stateII, err = stateII.CalculateNextState() if err != nil { - return problem.Solution{}, + return simplex_solution.SimplexSolution{}, fmt.Errorf( "There was an issue updating the state at iteration %v: %v", iter, diff --git a/algorithms/tableau/termination/termination_types.go b/algorithms/tableau/termination/termination_types.go index 0a3bb80..1cf15cd 100644 --- a/algorithms/tableau/termination/termination_types.go +++ b/algorithms/tableau/termination/termination_types.go @@ -1,7 +1,7 @@ package tableau_termination import ( - "github.com/MatProGo-dev/MatProInterface.go/problem" + solution_status "github.com/MatProGo-dev/MatProInterface.go/solution/status" ) type TerminationType string @@ -10,15 +10,15 @@ const DidNotTerminate TerminationType = "Did Not Terminate" const MaximumIterationsReached TerminationType = "Maximum Iterations Reached" const OptimalSolutionFound TerminationType = "Optimal Solution Found" -func (tt TerminationType) ToOptimizationStatus() problem.OptimizationStatus { +func (tt TerminationType) ToOptimizationStatus() solution_status.SolutionStatus { switch tt { case DidNotTerminate: - return problem.OptimizationStatus_INPROGRESS + return solution_status.INPROGRESS case MaximumIterationsReached: - return problem.OptimizationStatus_ITERATION_LIMIT + return solution_status.ITERATION_LIMIT case OptimalSolutionFound: - return problem.OptimizationStatus_OPTIMAL + return solution_status.OPTIMAL default: - return problem.OptimizationStatus_INPROGRESS + return solution_status.INPROGRESS } } diff --git a/examples/box1/box.go b/examples/box1/box.go new file mode 100644 index 0000000..ba50c25 --- /dev/null +++ b/examples/box1/box.go @@ -0,0 +1,89 @@ +package main + +import ( + "github.com/MatProGo-dev/MatProInterface.go/problem" + "github.com/MatProGo-dev/MatProInterface.go/solution" + getKVector "github.com/MatProGo-dev/SymbolicMath.go/get/KVector" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" + "github.com/MatProGo-dev/simplex/simplexSolver" +) + +/* +Description: + + This function builds an optimization problem where we attempt to find + the optimal solution to a linear programming problem that is in a feasible + region that is a box. + + The problem will be: + + Minimize: x1 + 2*x2 + Subject to: + 0 <= x1 <= 1 + 0 <= x2 <= 1 + + The optimal solution is x1 = 0, x2 = 0 with an objective value of 0. +*/ +func BuildOptimizationProblem() problem.OptimizationProblem { + // setup + varCount := 2 + out := problem.NewProblem("Box LP Problem") + + // Create the variables + x := out.AddVariableVector(varCount) + + // Create the objective + c := getKVector.From( + []float64{1, 2}, + ) + out.SetObjective( + c.Transpose().Multiply(x), + problem.SenseMinimize, + ) + + // Create the constraints + // - x >= 0 + out.Constraints = append( + out.Constraints, + x.GreaterEq(symbolic.ZerosVector(varCount)), + ) + + // - x <= 1 + out.Constraints = append( + out.Constraints, + x.LessEq(symbolic.OnesVector(varCount)), + ) + + return *out +} + +func main() { + // This is just a placeholder to make the package "main" valid. + trickyProblem := BuildOptimizationProblem() + + // Use solver to solve the problem + solver := simplexSolver.New("Simplex Solver Example") + solver.IterationLimit = 100 + + // Solve the problem + sol, err := solver.Solve(trickyProblem) + if err != nil { + panic(err) + } + + // Print the solution + solutionMessage, _ := sol.Status.ToMessage() + println("Solution Status: ", solutionMessage) + + optObj, err := solution.GetOptimalObjectiveValue(&sol) + if err != nil { + panic(err) + } + println("Objective Value: ", optObj) + + println("Number of Iterations: ", sol.Iterations) + println("Variable Values: ") + for varName, varValue := range sol.VariableValues { + println(" ", varName, ": ", varValue) + } +} diff --git a/examples/box2/box.go b/examples/box2/box.go new file mode 100644 index 0000000..efb14ff --- /dev/null +++ b/examples/box2/box.go @@ -0,0 +1,88 @@ +package main + +import ( + "github.com/MatProGo-dev/MatProInterface.go/problem" + "github.com/MatProGo-dev/MatProInterface.go/solution" + getKVector "github.com/MatProGo-dev/SymbolicMath.go/get/KVector" + "github.com/MatProGo-dev/SymbolicMath.go/symbolic" + "github.com/MatProGo-dev/simplex/simplexSolver" + "gonum.org/v1/gonum/mat" +) + +/* +Description: + + This function builds an optimization problem where we attempt to find + the optimal solution to a linear programming problem that is in a feasible + region that is a box. + + The problem will be: + + Minimize: x1 - 2*x2 + Subject to: + -1 <= x1 <= 1 + -1 <= x2 <= 1 + + The optimal solution is x1 = -1, x2 = 1 with an objective value of -3. +*/ +func BuildOptimizationProblem() problem.OptimizationProblem { + // setup + varCount := 2 + out := problem.NewProblem("Box LP Problem") + + // Create the variables + x := out.AddVariableVector(varCount) + + // Create the objective + c := getKVector.From( + []float64{1, -2}, + ) + out.SetObjective( + c.Transpose().Multiply(x), + problem.SenseMinimize, + ) + + // Create the constraints + // - x >= -1 + out.Constraints = append( + out.Constraints, + x.GreaterEq(mat.NewVecDense(varCount, []float64{-1, -1})), + ) + + // - x <= 1 + out.Constraints = append( + out.Constraints, + x.LessEq(symbolic.OnesVector(varCount)), + ) + + return *out +} + +func main() { + // This is just a placeholder to make the package "main" valid. + trickyProblem := BuildOptimizationProblem() + + // Use solver to solve the problem + solver := simplexSolver.New("Simplex Solver Example") + solver.IterationLimit = 100 + + // Solve the problem + sol, err := solver.Solve(trickyProblem) + if err != nil { + panic(err) + } + + // Print the solution + solutionMessage, _ := sol.Status.ToMessage() + println("Solution Status: ", solutionMessage) + optObj, err := solution.GetOptimalObjectiveValue(&sol) + if err != nil { + panic(err) + } + println("Objective Value: ", optObj) + println("Number of Iterations: ", sol.Iterations) + println("Variable Values: ") + for varName, varValue := range sol.VariableValues { + println(" ", varName, ": ", varValue) + } +} diff --git a/examples/gonum_bug1/try_bug.go b/examples/gonum_bug1/try_bug.go new file mode 100644 index 0000000..a16bd6d --- /dev/null +++ b/examples/gonum_bug1/try_bug.go @@ -0,0 +1,98 @@ +package main + +import ( + "github.com/MatProGo-dev/MatProInterface.go/problem" + "github.com/MatProGo-dev/MatProInterface.go/solution" + getKMatrix "github.com/MatProGo-dev/SymbolicMath.go/get/KMatrix" + getKVector "github.com/MatProGo-dev/SymbolicMath.go/get/KVector" + "github.com/MatProGo-dev/simplex/simplexSolver" + "gonum.org/v1/gonum/mat" +) + +/* +Description: + + Builds a version of a linear programming problem mentioned in this issue in gonum: + + https://github.com/gonum/gonum/issues/1914 + + This problem is very large and was causing issues in Gonum's LP solver. + + We will use this problem to test our own simplex solver implementation. +*/ +func BuildOptimizationProblem() problem.OptimizationProblem { + // setup + varCount := 57 + out := problem.NewProblem("Gonum Bug LP Problem") + + // Create the variables + x := out.AddVariableVector(varCount) + + // Create the objective + c := getKVector.From( + []float64{10, 5, 7, 10, 10, 5, 20, 20, 20, 20, 7, 5, 10, 80, 80, 10, 5, 5, 80, 80, 10, 80, 10, 7, 10, 10, 80, 7, 10, 20, 10, 7, 10, 7, 10, 15, 10, 10, 80, 10, 5, 7, 5, 10, 20, 10, 5, 10, 80, 80, 5, 10, 7, 7, 10, 5, 7}, + ) + out.SetObjective( + c.Transpose().Multiply(x), + problem.SenseMinimize, + ) + + // Create the constraints + // - Linear Equality Constraints + b := getKVector.From( + []float64{38, 5, 2, 33, 28, 14, 2, 48, 8, 133, 117, 34, 48, 20, 16, 50, 30, 75, 10, 40, 6, 70, 10, 5, 167, 13, 2, 118, 12, 98, 67, 157, 55, 2, 4, 4, 25, 4}, + ) + + Abuf := []float64{ + 1, 0.95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.95, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0.95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0.95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0.95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0.95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0.95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0.95, 0.95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0.95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.95, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0.95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0.95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.95, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0.95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.95, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.95, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.95, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0.95, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.95} + A := mat.NewDense(38, varCount, Abuf) + + out.Constraints = append( + out.Constraints, + getKMatrix.From(A).Multiply(x).Eq(b), + ) + + // - Linear Inequality Constraints + h := getKVector.From( + []float64{3370, 1031, 2350, 2289, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + ) + Gbuf := []float64{1, 0, 0, 1, 1, 0, 2, 2, 0, 2, 0, 0, 1, 0, 8, 1, 0, 0, 8, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 2, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 2, 0, 0, 1, 0, 8, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 8, 0, 8, 1, 0, 0, 1, 8, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 2, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1} + G := mat.NewDense(63, varCount, Gbuf) + + out.Constraints = append( + out.Constraints, + getKMatrix.From(G).Multiply(x).LessEq(h), + ) + + return *out + +} + +func main() { + // This is just a placeholder to make the package "main" valid. + trickyProblem := BuildOptimizationProblem() + + // Use solver to solve the problem + solver := simplexSolver.New("Simplex Solver Example") + solver.IterationLimit = 100 + + // Solve the problem + sol, err := solver.Solve(trickyProblem) + if err != nil { + panic(err) + } + + // Print the solution + solutionMessage, _ := sol.Status.ToMessage() + println("Solution Status: ", solutionMessage) + optObj, err := solution.GetOptimalObjectiveValue(&sol) + if err != nil { + panic(err) + } + println("Objective Value: ", optObj) + println("Number of Iterations: ", sol.Iterations) + println("Variable Values: ") + for varName, varValue := range sol.VariableValues { + println(" ", varName, ": ", varValue) + } +} diff --git a/examples/sergiy_butenko1/the_simplex_method_part_ii.go b/examples/sergiy_butenko1/the_simplex_method_part_ii.go index ba062a9..6d8a9eb 100644 --- a/examples/sergiy_butenko1/the_simplex_method_part_ii.go +++ b/examples/sergiy_butenko1/the_simplex_method_part_ii.go @@ -1,6 +1,7 @@ package main import ( + "github.com/MatProGo-dev/MatProInterface.go/solution" "github.com/MatProGo-dev/simplex/simplexSolver" "github.com/MatProGo-dev/simplex/utils/examples" ) @@ -17,17 +18,19 @@ func main() { solver.IterationLimit = 100 // Solve the problem - solution, err := solver.Solve(*problem5) + sol, err := solver.Solve(*problem5) if err != nil { panic(err) } // Print the solution - solutionMessage, _ := solution.Status.ToMessage() + solutionMessage, _ := sol.Status.ToMessage() println("Solution Status: ", solutionMessage) - println("Objective Value: ", solution.Objective) + optVal, _ := solution.GetOptimalObjectiveValue(&sol) + println("Objective Value: ", optVal) + println("Number of Iterations: ", sol.Iterations) println("Variable Values: ") - for varName, varValue := range solution.Values { + for varName, varValue := range sol.VariableValues { println(" ", varName, ": ", varValue) } } diff --git a/go.mod b/go.mod index 583e021..194d383 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.0 toolchain go1.23.9 require ( - github.com/MatProGo-dev/MatProInterface.go v0.5.5 - github.com/MatProGo-dev/SymbolicMath.go v0.2.4-1 + github.com/MatProGo-dev/MatProInterface.go v0.6.5-1 + github.com/MatProGo-dev/SymbolicMath.go v0.3.2-0.20251017032605-a7dee10c22bf gonum.org/v1/gonum v0.16.0 ) diff --git a/go.sum b/go.sum index fca3c9b..2c52a4f 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ -github.com/MatProGo-dev/MatProInterface.go v0.5.5 h1:mq4hxTXz9msY0hEKqrKLJB4Ne0jfv4itaZAx3LV/l7A= -github.com/MatProGo-dev/MatProInterface.go v0.5.5/go.mod h1:UtPWsYyLFvssLYPZkiyMpgb9R9aTL8J4y1GfrM4phvc= -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/MatProInterface.go v0.6.5-1 h1:dOpwi8mQiHSkA2oXY1B2v/uOM/3ZvZSwbmMomsMLakY= +github.com/MatProGo-dev/MatProInterface.go v0.6.5-1/go.mod h1:Ip6cN6/uT39LSc6XCqIPnwXLq9N+T/4nLtDPPmHYwhA= +github.com/MatProGo-dev/SymbolicMath.go v0.3.2-0.20251017032605-a7dee10c22bf h1:fvHlqO39XHb/TfMm979zsnrcvZDSAFU7PW61g1UQCW8= +github.com/MatProGo-dev/SymbolicMath.go v0.3.2-0.20251017032605-a7dee10c22bf/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= diff --git a/simplexSolver/solver.go b/simplexSolver/solver.go index e0c9fa4..4277a53 100644 --- a/simplexSolver/solver.go +++ b/simplexSolver/solver.go @@ -6,6 +6,7 @@ import ( "github.com/MatProGo-dev/MatProInterface.go/problem" "github.com/MatProGo-dev/simplex/algorithms" tableau_algorithm1 "github.com/MatProGo-dev/simplex/algorithms/tableau" + simplex_solution "github.com/MatProGo-dev/simplex/solution" ) type SimplexSolver struct { @@ -39,13 +40,13 @@ func (solver *SimplexSolver) CreateAlgorithm(algoType algorithms.AlgorithmType) } } -func (solver *SimplexSolver) Solve(prob problem.OptimizationProblem) (problem.Solution, error) { +func (solver *SimplexSolver) Solve(prob problem.OptimizationProblem) (simplex_solution.SimplexSolution, error) { // Setup // Choose Algorithm algo, err := solver.CreateAlgorithm(solver.Algorithm) if err != nil { - return problem.Solution{}, fmt.Errorf( + return simplex_solution.SimplexSolution{}, fmt.Errorf( "The Solve() function was given an unknown solver type: %v", solver.Algorithm, ) diff --git a/solution/simplex_solution.go b/solution/simplex_solution.go new file mode 100644 index 0000000..def27e0 --- /dev/null +++ b/solution/simplex_solution.go @@ -0,0 +1,42 @@ +package simplex_solution + +import ( + "github.com/MatProGo-dev/MatProInterface.go/problem" + "github.com/MatProGo-dev/MatProInterface.go/solution" + solution_status "github.com/MatProGo-dev/MatProInterface.go/solution/status" +) + +// SimplexSolution represents the result of solving a linear program using the simplex method. +// It contains the values of the decision variables, the objective value, and the solution status. +type SimplexSolution struct { + // VariableValues maps variable IDs (as uint64) to their solution values. + // The uint64 key typically represents the unique identifier or index of a variable in the model. + VariableValues map[uint64]float64 + // Status indicates the status of the solution (e.g., optimal, infeasible). + Status solution_status.SolutionStatus + Iterations int + // originalProblem is the original optimization problem that was solved to obtain this solution. + // It is included for reference and may be nil if not applicable. + OriginalProblem *problem.OptimizationProblem +} + +func (sol *SimplexSolution) GetValueMap() map[uint64]float64 { + return sol.VariableValues +} + +func (sol *SimplexSolution) GetOptimalValue() float64 { + // Use the symbolic.Solution interface to compute the optimal value + optVal, err := solution.GetOptimalObjectiveValue(sol) + if err != nil { + return 0.0 + } + return optVal +} + +func (sol *SimplexSolution) GetStatus() solution_status.SolutionStatus { + return sol.Status +} + +func (sol *SimplexSolution) GetProblem() *problem.OptimizationProblem { + return sol.OriginalProblem +} diff --git a/testing/algorithms/stanford/state_test.go b/testing/algorithms/stanford/state_test.go index 721995f..30bf0e5 100644 --- a/testing/algorithms/stanford/state_test.go +++ b/testing/algorithms/stanford/state_test.go @@ -16,7 +16,7 @@ func TestStanfordAlgorithmState_NonBasicVariables1(t *testing.T) { exampleProblem1 := examples.GetTestProblem1() // Create the problem in standard form - problemInStandardForm, slackVariables, err := exampleProblem1.ToLPStandardForm1() + problemInStandardForm, slackVariables, _, err := exampleProblem1.ToLPStandardForm1() if err != nil { t.Errorf("Expected no error, but got: %v", err) } @@ -80,7 +80,7 @@ func TestStanfordAlgorithmState_BasicVariables1(t *testing.T) { exampleProblem1 := examples.GetTestProblem1() // Create the problem in standard form - problemInStandardForm, slackVariables, err := exampleProblem1.ToLPStandardForm1() + problemInStandardForm, slackVariables, _, err := exampleProblem1.ToLPStandardForm1() if err != nil { t.Errorf("Expected no error, but got: %v", err) } @@ -147,7 +147,7 @@ func TestStanfordAlgorithmState_ReducedCostVector1(t *testing.T) { exampleProblem1 := examples.GetTestProblem2() // Create the problem in standard form - problemInStandardForm, slackVariables, err := exampleProblem1.ToLPStandardForm1() + problemInStandardForm, slackVariables, _, err := exampleProblem1.ToLPStandardForm1() if err != nil { t.Errorf("Expected no error, but got: %v", err) } diff --git a/testing/algorithms/tableau/state_test.go b/testing/algorithms/tableau/state_test.go index de7d526..4d8c7a7 100644 --- a/testing/algorithms/tableau/state_test.go +++ b/testing/algorithms/tableau/state_test.go @@ -34,7 +34,7 @@ func TestTableau_CalculateOptimalSolution1(t *testing.T) { // Use the optimization solver to solve the problem // Create initial Tableau state from the problem - initialTableau, err := utils.GetInitialTableauFrom(testProblem) + initialTableau, _, err := utils.GetInitialTableauFrom(testProblem) if err != nil { t.Errorf("there was an issue creating the initial tableau: %v", err) } @@ -66,5 +66,4 @@ func TestTableau_CalculateOptimalSolution1(t *testing.T) { t.Errorf("Expected solution value %v at index %d, but got %v", val, ii, solVec.AtVec(ii)) } } - } diff --git a/testing/utils/tableau_test.go b/testing/utils/tableau_test.go index 64740c1..2803efe 100644 --- a/testing/utils/tableau_test.go +++ b/testing/utils/tableau_test.go @@ -24,7 +24,7 @@ func TestGetInitialTableau1(t *testing.T) { problemIn := examples.GetTestProblem3() // Create the tableau using the initial state + problem in standard form - tableau, err := utils.GetInitialTableauFrom(problemIn) + tableau, _, err := utils.GetInitialTableauFrom(problemIn) if err != nil { t.Errorf("Expected no error, but got: %v", err) } @@ -83,7 +83,7 @@ func TestComputeFeasibleSolution1(t *testing.T) { problemIn := examples.GetTestProblem3() // Create the tableau - tableau, err := utils.GetInitialTableauFrom(problemIn) + tableau, _, err := utils.GetInitialTableauFrom(problemIn) if err != nil { t.Errorf("Expected no error, but got: %v", err) } diff --git a/utils/tableau.go b/utils/tableau.go index 6562ae6..3ae4fb1 100644 --- a/utils/tableau.go +++ b/utils/tableau.go @@ -348,28 +348,30 @@ Description: This function computes the initial tableau of an */ -func GetInitialTableauFrom(problemIn *problem.OptimizationProblem) (Tableau, error) { +func GetInitialTableauFrom(problemIn *problem.OptimizationProblem) (Tableau, map[symbolic.Variable]symbolic.Expression, error) { // Input Processing if problemIn == nil { - return Tableau{}, fmt.Errorf( + return Tableau{}, nil, fmt.Errorf( "Check: tableau.Problem cannot be nil", ) } // Ensure that the problem is a linear program if !problemIn.IsLinear() { - return Tableau{}, fmt.Errorf( + return Tableau{}, nil, fmt.Errorf( "Check: the problem is not a linear program", ) } // Transform the problem into the standard form where all constraints // are equality constraints - problemInStandardForm, slackVariables, err := problemIn.ToLPStandardForm2() + problemInStandardForm, slackVariables, mapFromOriginalVariablesToNewExpressions, err := problemIn.ToLPStandardForm2() if err != nil { - return Tableau{}, err + return Tableau{}, nil, err } + fmt.Println("Problem in standard form:", problemInStandardForm) + // Transform SlackVariables object into indicies var slackVariableIndicies []int for _, slackVar := range slackVariables { @@ -381,7 +383,7 @@ func GetInitialTableauFrom(problemIn *problem.OptimizationProblem) (Tableau, err // [ A | b ] A, b, err := problemInStandardForm.LinearEqualityConstraintMatrices() if err != nil { - return Tableau{}, err + return Tableau{}, mapFromOriginalVariablesToNewExpressions, err } Ab := symbolic.HStack(A, b) @@ -408,7 +410,7 @@ func GetInitialTableauFrom(problemIn *problem.OptimizationProblem) (Tableau, err AsCompressedMatrix: &tableauMatCondensedAsDense, Variables: problemInStandardForm.Variables, BasicVariableIndicies: slackVariableIndicies, - }, nil + }, mapFromOriginalVariablesToNewExpressions, nil } /* @@ -446,6 +448,8 @@ func (tableau *Tableau) CanNotBeImproved() bool { // Get the coefficients of the non-basic variables c := tableau.C() + fmt.Println("c =", c) + // Check if all coefficients are less than or equal to zero for ii := 0; ii < c.Len(); ii++ { if c.AtVec(ii) < 0 { @@ -453,6 +457,28 @@ func (tableau *Tableau) CanNotBeImproved() bool { } } + // For each coefficient that is negative, check if there is a corresponding + // row in the Tableau matrix that has a positive ratio (i.e., b[i] / A[i, enteringVarIdx] > 0). + // If there is no such row for any entering variable, then the problem can not be improved. + A := tableau.A() + nRowsA, _ := A.Dims() + b := tableau.B() + for enteringVarIdx := 0; enteringVarIdx < c.Len(); enteringVarIdx++ { + if c.AtVec(enteringVarIdx) < 0 { + // Check for positive ratios + hasPositiveRatio := false + for rowIdx := 0; rowIdx < nRowsA; rowIdx++ { + if b.AtVec(rowIdx)/A.At(rowIdx, enteringVarIdx) > 0 { + hasPositiveRatio = true + break + } + } + if hasPositiveRatio { + return false + } + } + } + return true }