- Review on Linear Programs
- Variables
- Linear Expressions
- Constraints
- Linear Programs
- Linear Program Solutions
A Linear Program is a special case of a mathematical model which has requirements specified by linear relationships. Linear Programs are problems which must be able to be expressed in the following canonical form:
Not all Linear Programs are easily written in this form, and so KeLPie allows you to define and solve Linear Programs in any non-canonical form.
The heart of the Linear Program in KeLPie is the LpSolver class. The LpSolver takes in a linear expression
However, to make an LpSolver instance, we have to define some variables first for the ContinuousVar:
import krilis.solver.*
fun main() {
val x1 = ContinuousVar("x1")
val x2 = ContinuousVar("x2")
}Every ContinuousVar needs to have a name. If you create two ContinuousVars with the same name, they will refer
to the same Linear Program variable despite being separate instances of ContinuousVar.
We can do linear operations between these variables to create a linear Expression:
val x1 = ContinuousVar("x1")
val x2 = ContinuousVar("x2")
val x1Plusx2: LinearExpr = x1 + x2
val x1Minusx2: LinearExpr = x1 - x2You can add coefficients and constants.
val constantOffset: LinearExpr = x1 - 5.0
val coefficients: LinearExpr = -x2 * 3.0You can combine LinearExpr objects with other LinearExprs and other ContinousVars.
val le1: LinearExpr = x1 * 5.0
val le2: LinearExpr = x2 * -10.0 + 7.0
// Represents 5*(x1) - 10*(x2) + 7
val combined: LinearExpr = le1 + le2You can also convert individual variables to the LinearExpr type with a cast:
val x1 = ContinuousVar("x1")
val x1LPExpr = x1.toLinearExpr()KeLPie can handle both equalities and inequalities as constraints:
val x1 = ContinuousVar("x1")
val x2 = ContinuousVar("x2")
// Let x1 = x2
val equalityConstraint = EqConstraint(x1, x2)
// Let x1 >= x2
val gteConstraint = GteConstraint(x1, x2)
// Let x1 <= x2
val lteConstraint = LteConstraint(x1, x2)These work just the same with LinearExprs and Doubles as arguments.
// Let x1 + x2 = 0
val equalityConstraint = EqConstraint(x1 + x2, 0.0)Now that we can construct linear expressions and linear constraints, we can finally construct our linear program:
val x1 = ContinuousVar("x1")
val x2 = ContinuousVar("x2")
val p = x1 + x2
val lp = LpSolver(
maximize = p,
subjectTo = arrayListOf(
EqConstraint(x2, -x1 + 1.0),
GteConstraint(x1, 0.0),
GteConstraint(x2, 0.0)
)
)And solve it using the simplex algorithm:
val solution: ProblemSolution = lp.simplexSolve()There are three different kinds of solutions:
- A single answer; represents the solution which maximises the objective function.
- No feasible solution; the Linear Program is infeasible.
- Unbounded solution; the objective function can increase infinitely and is unbounded.
These are respectively represented with the following ProgramSolution child types:
OptimalSolutionInfeasibleSolutionUnboundedSolution
Only OptimalSolution has useful information inside it besides the type. You can then use the resulting
OptimalSolution object to ask questions about the solution.
You can retrieve the maximum objective function value using with the objectiveValue property.
val optimizationValue: Double = solution.objectiveValueYou can retrieve variable values if you have a reference to the ContinuousVar.
val x1 = ContinuousVar("x1")
// Get a solution here ...
val x1Result: Double = solution.getValue(x1)You can also retrieve variable values if you know the name of the variable:
val x1Result: Double = solution.getValue("x1")Some solution handling code may look like:
val solution: ProblemSolution = lp.simplexSolve()
when (solution) {
is OptimalSolution -> {
println("Optimal solution found!")
prinln("Objective function had value: ${solution.objectiveValue}")
println("And variable x1 had value: ${solution.valueOf("x1")}")
}
is InfeasibleSolution -> println("No solution found, problem is infeasible.")
is UnboundedSolution -> println("No optimal solution found, objective is unbounded.")
}