Package com.enterprisemath.math.lp

Source Code of com.enterprisemath.math.lp.StandardLPSimplexSolver

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;
import com.enterprisemath.math.utils.MathUtils;

/**
* This is the implementation of the standard simplex method.
* This method is used to solve the linear problem in a standard form.
*
* @author radek.hecl
*
*/
public class StandardLPSimplexSolver {

    /**
     * Builder object.
     */
    public static class Builder {

        /**
         * Builds the result object.
         *
         * @return created object
         */
        public StandardLPSimplexSolver build() {
            return new StandardLPSimplexSolver(this);
        }
    }

    /**
     * Creates new instance.
     *
     * @param builder builder object
     */
    public StandardLPSimplexSolver(Builder builder) {
        guardInvariants();
    }

    /**
     * Guards this object to be consistent. Throws exception if this is not the case.
     */
    private void guardInvariants() {

    }

    /**
     * Solves the linear programming problem in a standard form and returns the result.
     *
     * @param lpp linear programming problem in the standard form
     * @return solution result
     */
    public synchronized StandardLPProblemResult solve(StandardLPProblem lpp) {
        SimplexTable table = new SimplexTable(lpp);
        //System.out.print(table);

        while (!table.isOptimal()) {
            if (!table.iterate()) {
                // function is unbounded
                return new StandardLPProblemResult.Builder().
                        setValue(Double.POSITIVE_INFINITY).
                        build();
            }
            //System.out.print(table);
        }

        return collectAllResults(table, lpp);
    }

    /**
     * Collects all results from the table.
     *
     * @param table table
     * @param lpp linear programming problem
     * @return result
     */
    private StandardLPProblemResult collectAllResults(SimplexTable table, StandardLPProblem lpp) {
        double maxZ = table.getObjectiveFunctionValue();

        // find possible elements of basic solutions which possibly doesn't change the value of objective function
        Set<Integer> possibleBasic = table.getPossibleBasicVariables();
        Set<Set<Integer>> possibleSols = MathUtils.createCombinations(possibleBasic, lpp.getNumConstraints());

        //System.out.println(possibleSols);

        StandardLPProblemResult.Builder res = new StandardLPProblemResult.Builder();
        res.setValue(maxZ);

        for (Set<Integer> pb : possibleSols) {
            table.swapBasicVariables(pb);
            if (table.isOptimal()) {
                Vector sol = table.getSolution();
                res.addSolution(sol);
            }
        }

        return res.build();
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }

    /**
     * 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<Integer> newBasic) {
            Map<Integer, Integer> var2Row = new HashMap<Integer, Integer>();
            for (int i = 0; i < numRows - 1; ++i) {
                var2Row.put((int)table[i][numCols - 1], i);
            }
            List<Integer> entering = new ArrayList<Integer>(newBasic);
            entering.removeAll(var2Row.keySet());
            List<Integer> leaving = new ArrayList<Integer>(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<Integer> pColCands = new ArrayList<Integer>(numCols - 2);
            List<Integer> pRowCands = new ArrayList<Integer>(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<Integer> getPossibleBasicVariables() {
            Set<Integer> res = new TreeSet<Integer>();
            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;
        }

    }
}
TOP

Related Classes of com.enterprisemath.math.lp.StandardLPSimplexSolver

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.