359 lines
13 KiB
Forth
359 lines
13 KiB
Forth
namespace Google.OrTools
|
|
|
|
module FSharp =
|
|
|
|
open System
|
|
open Google.OrTools.Algorithms
|
|
open Google.OrTools.LinearSolver
|
|
|
|
type Goal =
|
|
/// Maximize the Objective Function
|
|
| Maximize
|
|
/// Minimize the Objective Function
|
|
| Minimize
|
|
|
|
/// Linear Programming Algorithm
|
|
type LinearProgramming =
|
|
/// Coin-Or (Recommended default)
|
|
| CLP
|
|
/// GNU Linear Programming Kit
|
|
| GLPK
|
|
/// Google Linear Optimization
|
|
| GLOP
|
|
/// Gurobi Optimizer
|
|
| GUROBI
|
|
/// IBM CPLEX
|
|
| CPLEX
|
|
/// Solver Name
|
|
override this.ToString() =
|
|
match this with
|
|
| CLP -> "CLP_LINEAR_PROGRAMMING"
|
|
| GLPK -> "GLPK_LINEAR_PROGRAMMING"
|
|
| GLOP -> "GLOP_LINEAR_PROGRAMMING"
|
|
| GUROBI -> "GUROBI_LINEAR_PROGRAMMING"
|
|
| CPLEX -> "CPLEX_LINEAR_PROGRAMMING"
|
|
/// Solver Id
|
|
member this.Id =
|
|
match this with
|
|
| CLP -> 0
|
|
| GLPK -> 1
|
|
| GLOP -> 2
|
|
| GUROBI -> 6
|
|
| CPLEX -> 10
|
|
|
|
/// Integer Programming Algoritm
|
|
type IntegerProgramming =
|
|
/// Solving Constraint Integer Programs (Recommended default)
|
|
| SCIP
|
|
/// GNU Linear Programming Kit
|
|
| GLPK
|
|
/// Coin-Or Branch and Cut
|
|
| CBC
|
|
/// Gurobi Optimizer
|
|
| GUROBI
|
|
/// IBM CPLEX
|
|
| CPLEX
|
|
/// Binary Optimizer
|
|
| BOP
|
|
/// Solver Name
|
|
override this.ToString() =
|
|
match this with
|
|
| SCIP -> "SCIP_MIXED_INTEGER_PROGRAMMING"
|
|
| GLPK -> "GLPK_MIXED_INTEGER_PROGRAMMING"
|
|
| CBC -> "CBC_MIXED_INTEGER_PROGRAMMING"
|
|
| GUROBI -> "GUROBI_MIXED_INTEGER_PROGRAMMING"
|
|
| CPLEX -> "CPLEX_MIXED_INTEGER_PROGRAMMING"
|
|
| BOP -> "BOP_INTEGER_PROGRAMMING"
|
|
/// Solver Id
|
|
member this.Id =
|
|
match this with
|
|
| SCIP -> 3
|
|
| GLPK -> 4
|
|
| CBC -> 5
|
|
| GUROBI -> 7
|
|
| CPLEX -> 11
|
|
| BOP -> 12
|
|
|
|
type LinearSolverAlgorithm =
|
|
| LP of LinearProgramming
|
|
| IP of IntegerProgramming
|
|
|
|
/// Max Flow Solver Result
|
|
type MaximumFlowResult =
|
|
| Optimal
|
|
| IntegerOverflow
|
|
| BadInput
|
|
| BadResult
|
|
member this.Id =
|
|
match this with
|
|
| Optimal -> 0
|
|
| IntegerOverflow -> 1
|
|
| BadInput -> 2
|
|
| BadResult -> 3
|
|
|
|
/// Minimum Cost Flow Result
|
|
type MinimumCostFlowResult =
|
|
| NotSolved
|
|
| Optimal
|
|
| Feasible
|
|
| Infeasible
|
|
| Unbalanced
|
|
| BadResult
|
|
| BadCostRange
|
|
member this.Id =
|
|
match this with
|
|
| NotSolved -> 0
|
|
| Optimal -> 1
|
|
| Feasible -> 2
|
|
| Infeasible -> 3
|
|
| Unbalanced -> 4
|
|
| BadResult -> 5
|
|
| BadCostRange -> 6
|
|
|
|
|
|
/// Knapsack Solver Algorithm
|
|
type KnapsackSolverAlgorithm =
|
|
| BruteForce
|
|
| SixtyFourItems
|
|
| DynamicProgramming
|
|
| MultidimensionCBC
|
|
| MultidimensionGLPK
|
|
| MultidimensionBranchAndBound
|
|
| MultidimensionSCIP
|
|
/// Solver Id
|
|
member this.Id =
|
|
match this with
|
|
| BruteForce -> 0
|
|
| SixtyFourItems -> 1
|
|
| DynamicProgramming -> 2
|
|
| MultidimensionCBC -> 3
|
|
| MultidimensionGLPK -> 4
|
|
| MultidimensionBranchAndBound -> 5
|
|
| MultidimensionSCIP -> 6
|
|
|
|
let knapsackSolve (name: string) (solverAlgorithm:KnapsackSolverAlgorithm) (profits:int64 list) (weights:int64 list) (capacities:int64 list) =
|
|
// extract the specific algorithm so its Id can be used to create solver
|
|
let algorithm =
|
|
match solverAlgorithm with
|
|
| MultidimensionBranchAndBound ->
|
|
MultidimensionBranchAndBound.Id
|
|
| BruteForce ->
|
|
BruteForce.Id
|
|
| SixtyFourItems ->
|
|
SixtyFourItems.Id
|
|
| DynamicProgramming ->
|
|
DynamicProgramming.Id
|
|
| MultidimensionCBC ->
|
|
MultidimensionCBC.Id
|
|
| MultidimensionGLPK ->
|
|
MultidimensionGLPK.Id
|
|
| MultidimensionSCIP ->
|
|
MultidimensionSCIP.Id
|
|
|
|
let solver = new KnapsackSolver(algorithm, name)
|
|
|
|
// transform lists to compatible structures for C++ Solver
|
|
let profits = new KInt64Vector( List.toArray profits )
|
|
|
|
let weights =
|
|
let tempVector = new KInt64VectorVector(1)
|
|
let tempWeights = new KInt64Vector(List.toArray weights)
|
|
tempVector.Add(tempWeights)
|
|
tempVector
|
|
|
|
let capacities = new KInt64Vector (List.toArray capacities)
|
|
|
|
solver.Init(profits, weights, capacities)
|
|
solver
|
|
|
|
type SolverOpts = {
|
|
/// Name of the solver
|
|
SolverName: string;
|
|
/// Linear objective function vector
|
|
ObjectiveFunction: float list;
|
|
/// Matrix for linear inequality constraints
|
|
ConstraintMatrix: float list list option;
|
|
/// Upper Bound vector for linear inequality constraints
|
|
ConstraintVectorUpperBound: float list option;
|
|
/// Lower Bound vector for linear inequality constraints
|
|
ConstraintVectorLowerBound: float list option;
|
|
/// Vector of variable upper bounds
|
|
VariableUpperBound: float list;
|
|
/// Vector of variable lower bounds
|
|
VariableLowerBound: float list;
|
|
/// Matrix for linear equality constraints
|
|
ConstraintMatrixEq: float list list option;
|
|
/// Vector for linear equality constraints
|
|
ConstraintVectorEq: float list option;
|
|
/// Solver Algorithm to use
|
|
SolverAlgorithm: LinearSolverAlgorithm;
|
|
/// Solver Goal (Minimize/Maximize)
|
|
SolverGoal: Goal
|
|
} with
|
|
member this.Name(nm:string)=
|
|
{this with SolverName = nm}
|
|
member this.Objective(objVector:float list) =
|
|
{this with ObjectiveFunction = objVector}
|
|
member this.Matrix(mat:float list list) =
|
|
{this with ConstraintMatrix = Some(mat)}
|
|
member this.VectorUpperBound(vec:float list) =
|
|
{this with ConstraintVectorUpperBound = Some(vec)}
|
|
member this.VectorLowerBound(vec:float list) =
|
|
{this with ConstraintVectorLowerBound = Some(vec)}
|
|
member this.MatrixEq(mat:float list list) =
|
|
{this with ConstraintMatrixEq = Some(mat)}
|
|
member this.VectorEq(vec:float list) =
|
|
{this with ConstraintVectorEq = Some(vec)}
|
|
member this.VarUpperBound(ub:float list) =
|
|
{this with VariableUpperBound = ub}
|
|
member this.VarLowerBound(lb:float list) =
|
|
{this with VariableLowerBound = lb}
|
|
member this.Algorithm(algo:LinearSolverAlgorithm) =
|
|
{this with SolverAlgorithm = algo}
|
|
member this.Goal(goal:Goal) =
|
|
{this with SolverGoal = goal}
|
|
static member Default =
|
|
{
|
|
SolverName = "Solver";
|
|
ObjectiveFunction = [];
|
|
ConstraintMatrix = None;
|
|
ConstraintVectorUpperBound = None;
|
|
ConstraintVectorLowerBound = None;
|
|
VariableUpperBound = [];
|
|
VariableLowerBound = [];
|
|
ConstraintMatrixEq = None;
|
|
ConstraintVectorEq = None;
|
|
SolverAlgorithm = LP CLP
|
|
SolverGoal = Maximize
|
|
}
|
|
|
|
let lpSolve (solverOptions:SolverOpts) =
|
|
// extract the specific algorithm so its Id can be used to create solver
|
|
let algorithm =
|
|
match solverOptions.SolverAlgorithm with
|
|
| LP lp -> lp.Id
|
|
| IP ip -> ip.Id
|
|
|
|
let solver = new Solver(solverOptions.SolverName, algorithm)
|
|
|
|
// Detect errors on required parameters
|
|
if (solverOptions.ConstraintMatrix.IsNone && solverOptions.ConstraintVectorUpperBound.IsNone && solverOptions.ConstraintVectorLowerBound.IsNone)
|
|
&& (solverOptions.ConstraintMatrixEq.IsNone && solverOptions.ConstraintVectorEq.IsNone) then
|
|
failwith "Must provide at least one Matrix/Vector pair for inequality/equality contraints"
|
|
|
|
if solverOptions.ObjectiveFunction.IsEmpty then
|
|
failwith "Objective function cannot be empty"
|
|
|
|
if solverOptions.VariableUpperBound.IsEmpty then
|
|
failwith "Variable upper bound values cannot be empty"
|
|
|
|
if solverOptions.VariableLowerBound.IsEmpty then
|
|
failwith "Variable lower bound values cannot be empty"
|
|
|
|
// check inequality matrix dimensions against bounds
|
|
match (solverOptions.ConstraintMatrix, solverOptions.ConstraintVectorLowerBound, solverOptions.ConstraintVectorUpperBound) with
|
|
| _, Some lb, Some ub when lb.Length <> ub.Length ->
|
|
failwithf "Constraint vector dimensions should be equal.\nLower Bound Length: %i\nUpper Bound Length: %i" lb.Length ub.Length
|
|
| _ -> ()
|
|
|
|
// Check Objective function dimensions with variable bounds dimensions
|
|
match (solverOptions.ObjectiveFunction.Length, solverOptions.VariableLowerBound.Length, solverOptions.VariableUpperBound.Length) with
|
|
| _ , lb, ub when lb <> ub ->
|
|
failwithf "Variable vector dimensions should be equal.\nLower Bound Length: %i\nUpper Bound Length: %i" lb ub
|
|
| obj, _ , ub when obj <> ub ->
|
|
failwithf "Variable vector dimensions should be equal.\nUpper Bound Length: %i\nObjective Function Length: %i" ub obj
|
|
| obj, lb, _ when obj <> lb ->
|
|
failwithf "Variable vector dimensions should be equal.\nLower Bound Length: %i\nObjective Function Length: %i" lb obj
|
|
| _ -> ()
|
|
|
|
// Variables
|
|
let vars =
|
|
match solverOptions.SolverAlgorithm with
|
|
| LP lp ->
|
|
[ for i in 0 .. (solverOptions.VariableLowerBound.Length-1) -> solver.MakeNumVar(solverOptions.VariableLowerBound.[i], solverOptions.VariableUpperBound.[i], (sprintf "var[%i]" i ) ) ]
|
|
| IP ip ->
|
|
[ for i in 0 .. (solverOptions.VariableLowerBound.Length-1) -> solver.MakeIntVar(solverOptions.VariableLowerBound.[i], solverOptions.VariableUpperBound.[i], (sprintf "var[%i]" i ) ) ]
|
|
|
|
// Constraints
|
|
let cols = [ for i in 0 .. (solverOptions.VariableLowerBound.Length-1) -> i ] // generate column index selectors
|
|
|
|
// Inequality Constraints
|
|
match (solverOptions.ConstraintMatrix, solverOptions.ConstraintVectorLowerBound, solverOptions.ConstraintVectorUpperBound) with
|
|
| (None, Some lb, Some ub) ->
|
|
failwithf "Matrix for Equality Constraints undefined."
|
|
| (Some mat, None, Some ub) ->
|
|
for row = 0 to (ub.Length-1) do
|
|
// generate constraint operands based on indices
|
|
let constraintOperands = List.map (fun c -> vars.[c] * mat.[c].[row]) cols
|
|
let linearExp = List.reduce (+) constraintOperands
|
|
|
|
// create the constraint
|
|
let rangeConstraint = RangeConstraint(linearExp, Double.NegativeInfinity, ub.[row])
|
|
solver.Add(rangeConstraint) |> ignore
|
|
| (Some mat, Some lb, None) ->
|
|
for row = 0 to (lb.Length-1) do
|
|
// generate constraint operands based on indices
|
|
let constraintOperands = List.map (fun c -> vars.[c] * mat.[c].[row]) cols
|
|
let linearExp = List.reduce (+) constraintOperands
|
|
|
|
// create the constraint
|
|
let rangeConstraint = RangeConstraint(linearExp, lb.[row], Double.PositiveInfinity)
|
|
solver.Add(rangeConstraint) |> ignore
|
|
| (Some mat, Some lb, Some ub) ->
|
|
for row = 0 to (ub.Length-1) do
|
|
// generate constraint operands based on indices
|
|
let constraintOperands = List.map (fun c -> vars.[c] * mat.[c].[row]) cols
|
|
let linearExp = List.reduce (+) constraintOperands
|
|
|
|
// create the constraint
|
|
let rangeConstraint = RangeConstraint(linearExp, lb.[row], ub.[row])
|
|
solver.Add(rangeConstraint) |> ignore
|
|
| _ -> ()
|
|
|
|
|
|
// Equality Constraints
|
|
match (solverOptions.ConstraintMatrixEq, solverOptions.ConstraintVectorEq) with
|
|
| (None, Some b) ->
|
|
failwithf "Matrix for Equality Constraints undefined."
|
|
| (Some mat, Some vec) ->
|
|
for row = 0 to (vec.Length-1) do
|
|
// generate constraint operands based on indices
|
|
let constraintOperands = List.map (fun c -> vars.[c] * mat.[c].[row]) cols
|
|
let linearExp = List.reduce (+) constraintOperands
|
|
|
|
// create the constraint
|
|
let equalityConstraint = RangeConstraint(linearExp, vec.[row], vec.[row])
|
|
solver.Add(equalityConstraint) |> ignore
|
|
| _ -> ()
|
|
|
|
// Objective
|
|
let objectiveOperands = List.map (fun c -> solverOptions.ObjectiveFunction.[c] * vars.[c]) cols
|
|
let objectiveExp = List.reduce (+) objectiveOperands
|
|
|
|
match solverOptions.SolverGoal with
|
|
| Minimize ->
|
|
solver.Minimize(objectiveExp)
|
|
| Maximize ->
|
|
solver.Maximize(objectiveExp)
|
|
|
|
solver
|
|
|
|
/// Solves the optimization problem and prints the results to console
|
|
let SolverSummary (solver:Solver) =
|
|
let resultStatus = solver.Solve();
|
|
|
|
match resultStatus with
|
|
| status when status <> Solver.OPTIMAL ->
|
|
printfn "The problem does not have an optimal solution!"
|
|
exit 0
|
|
| _ ->
|
|
printfn "\nProblem solved in %d milliseconds" (solver.WallTime())
|
|
printfn "Iterations: %i\n" (solver.Iterations())
|
|
|
|
printfn "Objective: %f" (solver.Objective().Value())
|
|
for i in 0 .. (solver.NumVariables()-1) do
|
|
printfn "%-10s: %f " (sprintf "var[%i]" i) ((solver.LookupVariableOrNull(sprintf "var[%i]" i)).SolutionValue())
|
|
|
|
solver
|
|
|