All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.enterprisemath.math.lp.StandardLPSimplexFirstSolver Maven / Gradle / Ivy

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;
        }

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy