001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.commons.math.optimization.linear;
019    
020    import java.util.ArrayList;
021    import java.util.List;
022    
023    import org.apache.commons.math.optimization.OptimizationException;
024    import org.apache.commons.math.optimization.RealPointValuePair;
025    import org.apache.commons.math.util.MathUtils;
026    
027    
028    /**
029     * Solves a linear problem using the Two-Phase Simplex Method.
030     * @version $Revision: 812831 $ $Date: 2009-09-09 10:48:03 +0200 (mer. 09 sept. 2009) $
031     * @since 2.0
032     */
033    public class SimplexSolver extends AbstractLinearOptimizer {
034    
035        /** Default amount of error to accept in floating point comparisons. */
036        private static final double DEFAULT_EPSILON = 1.0e-6;
037    
038        /** Amount of error to accept in floating point comparisons. */
039        protected final double epsilon;
040    
041        /**
042         * Build a simplex solver with default settings.
043         */
044        public SimplexSolver() {
045            this(DEFAULT_EPSILON);
046        }
047    
048        /**
049         * Build a simplex solver with a specified accepted amount of error
050         * @param epsilon the amount of error to accept in floating point comparisons
051         */
052        public SimplexSolver(final double epsilon) {
053            this.epsilon = epsilon;
054        }
055    
056        /**
057         * Returns the column with the most negative coefficient in the objective function row.
058         * @param tableau simple tableau for the problem
059         * @return column with the most negative coefficient
060         */
061        private Integer getPivotColumn(SimplexTableau tableau) {
062            double minValue = 0;
063            Integer minPos = null;
064            for (int i = tableau.getNumObjectiveFunctions(); i < tableau.getWidth() - 1; i++) {
065                if (MathUtils.compareTo(tableau.getEntry(0, i), minValue, epsilon) < 0) {
066                    minValue = tableau.getEntry(0, i);
067                    minPos = i;
068                }
069            }
070            return minPos;
071        }
072    
073        /**
074         * Returns the row with the minimum ratio as given by the minimum ratio test (MRT).
075         * @param tableau simple tableau for the problem
076         * @param col the column to test the ratio of.  See {@link #getPivotColumn(SimplexTableau)}
077         * @return row with the minimum ratio
078         */
079        private Integer getPivotRow(SimplexTableau tableau, final int col) {
080            // create a list of all the rows that tie for the lowest score in the minimum ratio test
081            List<Integer> minRatioPositions = new ArrayList<Integer>();
082            double minRatio = Double.MAX_VALUE;
083            for (int i = tableau.getNumObjectiveFunctions(); i < tableau.getHeight(); i++) {
084                final double rhs = tableau.getEntry(i, tableau.getWidth() - 1);
085                final double entry = tableau.getEntry(i, col);
086                if (MathUtils.compareTo(entry, 0, epsilon) > 0) {
087                    final double ratio = rhs / entry;
088                    if (MathUtils.equals(ratio, minRatio, epsilon)) {
089                        minRatioPositions.add(i);
090                    } else if (ratio < minRatio) {
091                        minRatio = ratio;
092                        minRatioPositions = new ArrayList<Integer>();
093                        minRatioPositions.add(i);
094                    }
095                }
096            }
097    
098            if (minRatioPositions.size() == 0) {
099              return null;
100            } else if (minRatioPositions.size() > 1) {
101              // there's a degeneracy as indicated by a tie in the minimum ratio test
102              // check if there's an artificial variable that can be forced out of the basis
103              for (Integer row : minRatioPositions) {
104                for (int i = 0; i < tableau.getNumArtificialVariables(); i++) {
105                  int column = i + tableau.getArtificialVariableOffset();
106                  if (MathUtils.equals(tableau.getEntry(row, column), 1, epsilon) &&
107                      row.equals(tableau.getBasicRow(column))) {
108                    return row;
109                  }
110                }
111              }
112            }
113            return minRatioPositions.get(0);
114        }
115    
116        /**
117         * Runs one iteration of the Simplex method on the given model.
118         * @param tableau simple tableau for the problem
119         * @throws OptimizationException if the maximal iteration count has been
120         * exceeded or if the model is found not to have a bounded solution
121         */
122        protected void doIteration(final SimplexTableau tableau)
123            throws OptimizationException {
124    
125            incrementIterationsCounter();
126    
127            Integer pivotCol = getPivotColumn(tableau);
128            Integer pivotRow = getPivotRow(tableau, pivotCol);
129            if (pivotRow == null) {
130                throw new UnboundedSolutionException();
131            }
132    
133            // set the pivot element to 1
134            double pivotVal = tableau.getEntry(pivotRow, pivotCol);
135            tableau.divideRow(pivotRow, pivotVal);
136    
137            // set the rest of the pivot column to 0
138            for (int i = 0; i < tableau.getHeight(); i++) {
139                if (i != pivotRow) {
140                    double multiplier = tableau.getEntry(i, pivotCol);
141                    tableau.subtractRow(i, pivotRow, multiplier);
142                }
143            }
144        }
145    
146        /**
147         * Solves Phase 1 of the Simplex method.
148         * @param tableau simple tableau for the problem
149         * @exception OptimizationException if the maximal number of iterations is
150         * exceeded, or if the problem is found not to have a bounded solution, or
151         * if there is no feasible solution
152         */
153        protected void solvePhase1(final SimplexTableau tableau) throws OptimizationException {
154    
155            // make sure we're in Phase 1
156            if (tableau.getNumArtificialVariables() == 0) {
157                return;
158            }
159    
160            while (!tableau.isOptimal()) {
161                doIteration(tableau);
162            }
163    
164            // if W is not zero then we have no feasible solution
165            if (!MathUtils.equals(tableau.getEntry(0, tableau.getRhsOffset()), 0, epsilon)) {
166                throw new NoFeasibleSolutionException();
167            }
168        }
169    
170        /** {@inheritDoc} */
171        @Override
172        public RealPointValuePair doOptimize() throws OptimizationException {
173            final SimplexTableau tableau =
174                new SimplexTableau(function, linearConstraints, goal, nonNegative, epsilon);
175    
176            solvePhase1(tableau);
177            tableau.dropPhase1Objective();
178    
179            while (!tableau.isOptimal()) {
180                doIteration(tableau);
181            }
182            return tableau.getSolution();
183        }
184    
185    }