com.enterprisemath.math.lp.StandardLPSimplexFirstSolver Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of em-math Show documentation
Show all versions of em-math Show documentation
Advanced mathematical algorithms.
The newest version!
package com.enterprisemath.math.lp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.lang3.builder.ToStringBuilder;
import com.enterprisemath.math.algebra.Vector;
/**
* Implementation of solver which uses simplex method and returns only one optimal solution.
* Returned solution is the first one which is calculated as an optimal.
*
* @author radek.hecl
*/
public class StandardLPSimplexFirstSolver implements StandardLPProblemSolver {
/**
* Builder object.
*/
public static class Builder {
/**
* Builds the result object.
*
* @return created object
*/
public StandardLPSimplexFirstSolver build() {
return new StandardLPSimplexFirstSolver(this);
}
}
/**
* Creates new instance.
*
* @param builder builder object
*/
public StandardLPSimplexFirstSolver(Builder builder) {
guardInvariants();
}
/**
* Guards this object to be consistent. Throws exception if this is not the case.
*/
private void guardInvariants() {
}
@Override
public synchronized StandardLPProblemResult solve(StandardLPProblem lpp) {
SimplexTable table = new SimplexTable(lpp);
while (!table.isOptimal()) {
if (!table.iterate()) {
// function is unbounded
return new StandardLPProblemResult.Builder().
setValue(Double.POSITIVE_INFINITY).
build();
}
}
return new StandardLPProblemResult.Builder().
setValue(table.getObjectiveFunctionValue()).
addSolution(table.getSolution()).
build();
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
/**
* Creates new instance.
*
* @return created instance
*/
public static StandardLPSimplexFirstSolver create() {
return new StandardLPSimplexFirstSolver.Builder().
build();
}
/**
* Simplex table class. This is helper class which wraps common methods around table data.
*/
private static class SimplexTable {
/**
* Table data.
*/
private double[][] table;
/**
* Number of rows.
*/
private int numRows;
/**
* Number of columns.
*/
private int numCols;
/**
* Linear programming problem.
*/
private StandardLPProblem lpp;
/**
* Creates new instance.
*
* @param lpp linear programming problem
*/
public SimplexTable(StandardLPProblem lpp) {
// note: variables layout in table is (slack variables, original variables)
this.lpp = lpp;
table = new double[lpp.getNumConstraints() + 1][lpp.getNumVariables() + lpp.getNumConstraints() + 2];
int ri = 0;
for (Vector constraint : lpp.getConstraints()) {
table[ri][ri] = 1;
table[ri][lpp.getNumVariables() + lpp.getNumConstraints() + 1] = ri;
for (int j = 0; j < constraint.getDimension(); ++j) {
table[ri][lpp.getNumConstraints() + j] = constraint.getComponent(j);
}
++ri;
}
for (int j = 0; j < lpp.getNumVariables(); ++j) {
table[ri][ri + j] = -lpp.getFunction().getComponent(j);
}
numRows = table.length;
numCols = table[0].length;
}
/**
* Returns true if table is optimal, false otherwise.
*
* @return true if table is optimal, false otherwise
*/
public boolean isOptimal() {
for (int i = 0; i < numCols - 2; ++i) {
if (table[numRows - 1][i] < 0) {
return false;
}
}
for (int i = 0; i < numRows - 1; ++i) {
if (table[i][numCols - 2] < 0) {
return false;
}
}
return true;
}
/**
* Swaps basic component within the table.
*
* @param pivotRow pivot row (this is the component which becomes non basic)
* @param pivotColumn pivot column (this is the component which becomes basic)
*/
public void swapBasicVariable(int pivotRow, int pivotColumn) {
double pVal = table[pivotRow][pivotColumn];
for (int col = 0; col < numCols - 1; ++col) {
table[pivotRow][col] = table[pivotRow][col] / pVal;
}
table[pivotRow][pivotColumn] = 1;
// update other rows
for (int row = 0; row < numRows; ++row) {
if (row == pivotRow) {
continue;
}
double fact = -table[row][pivotColumn];
for (int col = 0; col < numCols - 1; ++col) {
table[row][col] = table[row][col] + table[pivotRow][col] * fact;
}
table[row][pivotColumn] = 0;
}
// update variable id
table[pivotRow][numCols - 1] = pivotColumn;
}
/**
* Swaps the basic variables into the new one.
*
* @param newBasic new basic variables
*/
public void swapBasicVariables(Set newBasic) {
Map var2Row = new HashMap();
for (int i = 0; i < numRows - 1; ++i) {
var2Row.put((int) table[i][numCols - 1], i);
}
List entering = new ArrayList(newBasic);
entering.removeAll(var2Row.keySet());
List leaving = new ArrayList(var2Row.keySet());
leaving.removeAll(newBasic);
for (int i = 0; i < entering.size(); ++i) {
swapBasicVariable(var2Row.get(leaving.get(i)), entering.get(i));
}
}
/**
* Makes one table iteration.
*
* @return true if iteration was successful, false otherwise
*/
public boolean iterate() {
int pRow = -1;
int pCol = -1;
List pColCands = new ArrayList(numCols - 2);
List pRowCands = new ArrayList(numRows - 1);
//
// find pivot
pCol = -1;
pRow = -1;
pColCands.clear();
pRowCands.clear();
// find candidates
for (int i = 0; i < numCols - 2; ++i) {
if (table[numRows - 1][i] < 0) {
pColCands.add(i);
}
}
for (int i = 0; i < numRows - 1; ++i) {
if (table[i][numCols - 2] < 0) {
pRowCands.add(i);
}
}
// evaluate candidates
if (!pColCands.isEmpty()) {
for (int colCand : pColCands) {
double min = Double.POSITIVE_INFINITY;
for (int r = 0; r < table.length - 1; ++r) {
if (table[r][colCand] > 0) {
double frac = table[r][numCols - 2] / table[r][colCand];
if (frac < min) {
pRow = r;
min = frac;
}
}
}
if (pRow != -1) {
pCol = colCand;
break;
}
}
}
else if (!pRowCands.isEmpty()) {
for (int rowCand : pRowCands) {
double min = Double.POSITIVE_INFINITY;
for (int c = 0; c < numCols - 2; ++c) {
if (table[rowCand][c] < 0) {
double frac = table[rowCand][numCols - 2] / table[rowCand][c];
if (frac < min) {
pCol = c;
min = frac;
}
}
}
if (pCol != -1) {
pRow = rowCand;
break;
}
}
}
else {
throw new RuntimeException("table is already optimal, unable to make an iteration: table = \n" + toString());
}
if (pRow == -1 || pCol == -1) {
return false;
}
//System.out.println("rowCandidates = " + pRowCands + ", columnCandidates = " + pColCands);
//System.out.println("pivot row = " + pRow + ", pivot column = " + pCol);
//
// iterate
swapBasicVariable(pRow, pCol);
return true;
}
/**
* Returns current value of the objective function.
*
* @return current value of the objective function
*/
public double getObjectiveFunctionValue() {
return table[numRows - 1][numCols - 2];
}
/**
* Returns current solution vector.
*
* @return current solution vector
*/
public Vector getSolution() {
double[] sol = new double[lpp.getNumVariables()];
for (int row = 0; row < numRows - 1; ++row) {
if (table[row][numCols - 1] >= lpp.getNumConstraints()) {
if (table[row][numCols - 2] == -0) {
table[row][numCols - 2] = 0;
}
sol[(int) table[row][numCols - 1] - lpp.getNumConstraints()] = table[row][numCols - 2];
}
}
return Vector.create(sol);
}
/**
* Returns set of variables which might be basic ones.
* Precondition is that simplex table is optimal.
* If no, then behavior is not defined.
*
* @return set of variables which might be basic ones
*/
public Set getPossibleBasicVariables() {
Set res = new TreeSet();
for (int i = 0; i < numCols - 2; ++i) {
if (table[numRows - 1][i] == 0) {
res.add(i);
}
}
return res;
}
@Override
public String toString() {
String res = "";
for (double[] row : table) {
res = res + Arrays.toString(row) + "\n";
}
res = res + "------\n";
return res;
}
}
}