Home | History | Annotate | Download | only in linear
      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one or more
      3  * contributor license agreements.  See the NOTICE file distributed with
      4  * this work for additional information regarding copyright ownership.
      5  * The ASF licenses this file to You under the Apache License, Version 2.0
      6  * (the "License"); you may not use this file except in compliance with
      7  * the License.  You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package org.apache.commons.math.optimization.linear;
     19 
     20 import java.io.IOException;
     21 import java.io.ObjectInputStream;
     22 import java.io.ObjectOutputStream;
     23 import java.io.Serializable;
     24 import java.util.ArrayList;
     25 import java.util.Collection;
     26 import java.util.HashSet;
     27 import java.util.List;
     28 import java.util.Set;
     29 
     30 import org.apache.commons.math.linear.Array2DRowRealMatrix;
     31 import org.apache.commons.math.linear.MatrixUtils;
     32 import org.apache.commons.math.linear.RealMatrix;
     33 import org.apache.commons.math.linear.RealVector;
     34 import org.apache.commons.math.optimization.GoalType;
     35 import org.apache.commons.math.optimization.RealPointValuePair;
     36 import org.apache.commons.math.util.MathUtils;
     37 
     38 /**
     39  * A tableau for use in the Simplex method.
     40  *
     41  * <p>
     42  * Example:
     43  * <pre>
     44  *   W |  Z |  x1 |  x2 |  x- | s1 |  s2 |  a1 |  RHS
     45  * ---------------------------------------------------
     46  *  -1    0    0     0     0     0     0     1     0   &lt;= phase 1 objective
     47  *   0    1   -15   -10    0     0     0     0     0   &lt;= phase 2 objective
     48  *   0    0    1     0     0     1     0     0     2   &lt;= constraint 1
     49  *   0    0    0     1     0     0     1     0     3   &lt;= constraint 2
     50  *   0    0    1     1     0     0     0     1     4   &lt;= constraint 3
     51  * </pre>
     52  * W: Phase 1 objective function</br>
     53  * Z: Phase 2 objective function</br>
     54  * x1 &amp; x2: Decision variables</br>
     55  * x-: Extra decision variable to allow for negative values</br>
     56  * s1 &amp; s2: Slack/Surplus variables</br>
     57  * a1: Artificial variable</br>
     58  * RHS: Right hand side</br>
     59  * </p>
     60  * @version $Revision: 922713 $ $Date: 2010-03-14 02:26:13 +0100 (dim. 14 mars 2010) $
     61  * @since 2.0
     62  */
     63 class SimplexTableau implements Serializable {
     64 
     65     /** Column label for negative vars. */
     66     private static final String NEGATIVE_VAR_COLUMN_LABEL = "x-";
     67 
     68     /** Serializable version identifier. */
     69     private static final long serialVersionUID = -1369660067587938365L;
     70 
     71     /** Linear objective function. */
     72     private final LinearObjectiveFunction f;
     73 
     74     /** Linear constraints. */
     75     private final List<LinearConstraint> constraints;
     76 
     77     /** Whether to restrict the variables to non-negative values. */
     78     private final boolean restrictToNonNegative;
     79 
     80     /** The variables each column represents */
     81     private final List<String> columnLabels = new ArrayList<String>();
     82 
     83     /** Simple tableau. */
     84     private transient RealMatrix tableau;
     85 
     86     /** Number of decision variables. */
     87     private final int numDecisionVariables;
     88 
     89     /** Number of slack variables. */
     90     private final int numSlackVariables;
     91 
     92     /** Number of artificial variables. */
     93     private int numArtificialVariables;
     94 
     95     /** Amount of error to accept in floating point comparisons. */
     96     private final double epsilon;
     97 
     98     /**
     99      * Build a tableau for a linear problem.
    100      * @param f linear objective function
    101      * @param constraints linear constraints
    102      * @param goalType type of optimization goal: either {@link GoalType#MAXIMIZE}
    103      * or {@link GoalType#MINIMIZE}
    104      * @param restrictToNonNegative whether to restrict the variables to non-negative values
    105      * @param epsilon amount of error to accept in floating point comparisons
    106      */
    107     SimplexTableau(final LinearObjectiveFunction f,
    108                    final Collection<LinearConstraint> constraints,
    109                    final GoalType goalType, final boolean restrictToNonNegative,
    110                    final double epsilon) {
    111         this.f                      = f;
    112         this.constraints            = normalizeConstraints(constraints);
    113         this.restrictToNonNegative  = restrictToNonNegative;
    114         this.epsilon                = epsilon;
    115         this.numDecisionVariables   = f.getCoefficients().getDimension() +
    116                                       (restrictToNonNegative ? 0 : 1);
    117         this.numSlackVariables      = getConstraintTypeCounts(Relationship.LEQ) +
    118                                       getConstraintTypeCounts(Relationship.GEQ);
    119         this.numArtificialVariables = getConstraintTypeCounts(Relationship.EQ) +
    120                                       getConstraintTypeCounts(Relationship.GEQ);
    121         this.tableau = createTableau(goalType == GoalType.MAXIMIZE);
    122         initializeColumnLabels();
    123     }
    124 
    125     /**
    126      * Initialize the labels for the columns.
    127      */
    128     protected void initializeColumnLabels() {
    129       if (getNumObjectiveFunctions() == 2) {
    130         columnLabels.add("W");
    131       }
    132       columnLabels.add("Z");
    133       for (int i = 0; i < getOriginalNumDecisionVariables(); i++) {
    134         columnLabels.add("x" + i);
    135       }
    136       if (!restrictToNonNegative) {
    137         columnLabels.add(NEGATIVE_VAR_COLUMN_LABEL);
    138       }
    139       for (int i = 0; i < getNumSlackVariables(); i++) {
    140         columnLabels.add("s" + i);
    141       }
    142       for (int i = 0; i < getNumArtificialVariables(); i++) {
    143         columnLabels.add("a" + i);
    144       }
    145       columnLabels.add("RHS");
    146     }
    147 
    148     /**
    149      * Create the tableau by itself.
    150      * @param maximize if true, goal is to maximize the objective function
    151      * @return created tableau
    152      */
    153     protected RealMatrix createTableau(final boolean maximize) {
    154 
    155         // create a matrix of the correct size
    156         int width = numDecisionVariables + numSlackVariables +
    157         numArtificialVariables + getNumObjectiveFunctions() + 1; // + 1 is for RHS
    158         int height = constraints.size() + getNumObjectiveFunctions();
    159         Array2DRowRealMatrix matrix = new Array2DRowRealMatrix(height, width);
    160 
    161         // initialize the objective function rows
    162         if (getNumObjectiveFunctions() == 2) {
    163             matrix.setEntry(0, 0, -1);
    164         }
    165         int zIndex = (getNumObjectiveFunctions() == 1) ? 0 : 1;
    166         matrix.setEntry(zIndex, zIndex, maximize ? 1 : -1);
    167         RealVector objectiveCoefficients =
    168             maximize ? f.getCoefficients().mapMultiply(-1) : f.getCoefficients();
    169         copyArray(objectiveCoefficients.getData(), matrix.getDataRef()[zIndex]);
    170         matrix.setEntry(zIndex, width - 1,
    171             maximize ? f.getConstantTerm() : -1 * f.getConstantTerm());
    172 
    173         if (!restrictToNonNegative) {
    174             matrix.setEntry(zIndex, getSlackVariableOffset() - 1,
    175                 getInvertedCoeffiecientSum(objectiveCoefficients));
    176         }
    177 
    178         // initialize the constraint rows
    179         int slackVar = 0;
    180         int artificialVar = 0;
    181         for (int i = 0; i < constraints.size(); i++) {
    182             LinearConstraint constraint = constraints.get(i);
    183             int row = getNumObjectiveFunctions() + i;
    184 
    185             // decision variable coefficients
    186             copyArray(constraint.getCoefficients().getData(), matrix.getDataRef()[row]);
    187 
    188             // x-
    189             if (!restrictToNonNegative) {
    190                 matrix.setEntry(row, getSlackVariableOffset() - 1,
    191                     getInvertedCoeffiecientSum(constraint.getCoefficients()));
    192             }
    193 
    194             // RHS
    195             matrix.setEntry(row, width - 1, constraint.getValue());
    196 
    197             // slack variables
    198             if (constraint.getRelationship() == Relationship.LEQ) {
    199                 matrix.setEntry(row, getSlackVariableOffset() + slackVar++, 1);  // slack
    200             } else if (constraint.getRelationship() == Relationship.GEQ) {
    201                 matrix.setEntry(row, getSlackVariableOffset() + slackVar++, -1); // excess
    202             }
    203 
    204             // artificial variables
    205             if ((constraint.getRelationship() == Relationship.EQ) ||
    206                     (constraint.getRelationship() == Relationship.GEQ)) {
    207                 matrix.setEntry(0, getArtificialVariableOffset() + artificialVar, 1);
    208                 matrix.setEntry(row, getArtificialVariableOffset() + artificialVar++, 1);
    209                 matrix.setRowVector(0, matrix.getRowVector(0).subtract(matrix.getRowVector(row)));
    210             }
    211         }
    212 
    213         return matrix;
    214     }
    215 
    216     /**
    217      * Get new versions of the constraints which have positive right hand sides.
    218      * @param originalConstraints original (not normalized) constraints
    219      * @return new versions of the constraints
    220      */
    221     public List<LinearConstraint> normalizeConstraints(Collection<LinearConstraint> originalConstraints) {
    222         List<LinearConstraint> normalized = new ArrayList<LinearConstraint>();
    223         for (LinearConstraint constraint : originalConstraints) {
    224             normalized.add(normalize(constraint));
    225         }
    226         return normalized;
    227     }
    228 
    229     /**
    230      * Get a new equation equivalent to this one with a positive right hand side.
    231      * @param constraint reference constraint
    232      * @return new equation
    233      */
    234     private LinearConstraint normalize(final LinearConstraint constraint) {
    235         if (constraint.getValue() < 0) {
    236             return new LinearConstraint(constraint.getCoefficients().mapMultiply(-1),
    237                                         constraint.getRelationship().oppositeRelationship(),
    238                                         -1 * constraint.getValue());
    239         }
    240         return new LinearConstraint(constraint.getCoefficients(),
    241                                     constraint.getRelationship(), constraint.getValue());
    242     }
    243 
    244     /**
    245      * Get the number of objective functions in this tableau.
    246      * @return 2 for Phase 1.  1 for Phase 2.
    247      */
    248     protected final int getNumObjectiveFunctions() {
    249         return this.numArtificialVariables > 0 ? 2 : 1;
    250     }
    251 
    252     /**
    253      * Get a count of constraints corresponding to a specified relationship.
    254      * @param relationship relationship to count
    255      * @return number of constraint with the specified relationship
    256      */
    257     private int getConstraintTypeCounts(final Relationship relationship) {
    258         int count = 0;
    259         for (final LinearConstraint constraint : constraints) {
    260             if (constraint.getRelationship() == relationship) {
    261                 ++count;
    262             }
    263         }
    264         return count;
    265     }
    266 
    267     /**
    268      * Get the -1 times the sum of all coefficients in the given array.
    269      * @param coefficients coefficients to sum
    270      * @return the -1 times the sum of all coefficients in the given array.
    271      */
    272     protected static double getInvertedCoeffiecientSum(final RealVector coefficients) {
    273         double sum = 0;
    274         for (double coefficient : coefficients.getData()) {
    275             sum -= coefficient;
    276         }
    277         return sum;
    278     }
    279 
    280     /**
    281      * Checks whether the given column is basic.
    282      * @param col index of the column to check
    283      * @return the row that the variable is basic in.  null if the column is not basic
    284      */
    285     protected Integer getBasicRow(final int col) {
    286         Integer row = null;
    287         for (int i = 0; i < getHeight(); i++) {
    288             if (MathUtils.equals(getEntry(i, col), 1.0, epsilon) && (row == null)) {
    289                 row = i;
    290             } else if (!MathUtils.equals(getEntry(i, col), 0.0, epsilon)) {
    291                 return null;
    292             }
    293         }
    294         return row;
    295     }
    296 
    297     /**
    298      * Removes the phase 1 objective function, positive cost non-artificial variables,
    299      * and the non-basic artificial variables from this tableau.
    300      */
    301     protected void dropPhase1Objective() {
    302         if (getNumObjectiveFunctions() == 1) {
    303             return;
    304         }
    305 
    306         List<Integer> columnsToDrop = new ArrayList<Integer>();
    307         columnsToDrop.add(0);
    308 
    309         // positive cost non-artificial variables
    310         for (int i = getNumObjectiveFunctions(); i < getArtificialVariableOffset(); i++) {
    311           if (MathUtils.compareTo(tableau.getEntry(0, i), 0, epsilon) > 0) {
    312             columnsToDrop.add(i);
    313           }
    314         }
    315 
    316         // non-basic artificial variables
    317         for (int i = 0; i < getNumArtificialVariables(); i++) {
    318           int col = i + getArtificialVariableOffset();
    319           if (getBasicRow(col) == null) {
    320             columnsToDrop.add(col);
    321           }
    322         }
    323 
    324         double[][] matrix = new double[getHeight() - 1][getWidth() - columnsToDrop.size()];
    325         for (int i = 1; i < getHeight(); i++) {
    326           int col = 0;
    327           for (int j = 0; j < getWidth(); j++) {
    328             if (!columnsToDrop.contains(j)) {
    329               matrix[i - 1][col++] = tableau.getEntry(i, j);
    330             }
    331           }
    332         }
    333 
    334         for (int i = columnsToDrop.size() - 1; i >= 0; i--) {
    335           columnLabels.remove((int) columnsToDrop.get(i));
    336         }
    337 
    338         this.tableau = new Array2DRowRealMatrix(matrix);
    339         this.numArtificialVariables = 0;
    340     }
    341 
    342     /**
    343      * @param src the source array
    344      * @param dest the destination array
    345      */
    346     private void copyArray(final double[] src, final double[] dest) {
    347         System.arraycopy(src, 0, dest, getNumObjectiveFunctions(), src.length);
    348     }
    349 
    350     /**
    351      * Returns whether the problem is at an optimal state.
    352      * @return whether the model has been solved
    353      */
    354     boolean isOptimal() {
    355         for (int i = getNumObjectiveFunctions(); i < getWidth() - 1; i++) {
    356             if (MathUtils.compareTo(tableau.getEntry(0, i), 0, epsilon) < 0) {
    357                 return false;
    358             }
    359         }
    360         return true;
    361     }
    362 
    363     /**
    364      * Get the current solution.
    365      *
    366      * @return current solution
    367      */
    368     protected RealPointValuePair getSolution() {
    369       int negativeVarColumn = columnLabels.indexOf(NEGATIVE_VAR_COLUMN_LABEL);
    370       Integer negativeVarBasicRow = negativeVarColumn > 0 ? getBasicRow(negativeVarColumn) : null;
    371       double mostNegative = negativeVarBasicRow == null ? 0 : getEntry(negativeVarBasicRow, getRhsOffset());
    372 
    373       Set<Integer> basicRows = new HashSet<Integer>();
    374       double[] coefficients = new double[getOriginalNumDecisionVariables()];
    375       for (int i = 0; i < coefficients.length; i++) {
    376           int colIndex = columnLabels.indexOf("x" + i);
    377           if (colIndex < 0) {
    378             coefficients[i] = 0;
    379             continue;
    380           }
    381           Integer basicRow = getBasicRow(colIndex);
    382           if (basicRows.contains(basicRow)) {
    383               // if multiple variables can take a given value
    384               // then we choose the first and set the rest equal to 0
    385               coefficients[i] = 0;
    386           } else {
    387               basicRows.add(basicRow);
    388               coefficients[i] =
    389                   (basicRow == null ? 0 : getEntry(basicRow, getRhsOffset())) -
    390                   (restrictToNonNegative ? 0 : mostNegative);
    391           }
    392       }
    393       return new RealPointValuePair(coefficients, f.getValue(coefficients));
    394     }
    395 
    396     /**
    397      * Subtracts a multiple of one row from another.
    398      * <p>
    399      * After application of this operation, the following will hold:
    400      *   minuendRow = minuendRow - multiple * subtrahendRow
    401      * </p>
    402      * @param dividendRow index of the row
    403      * @param divisor value of the divisor
    404      */
    405     protected void divideRow(final int dividendRow, final double divisor) {
    406         for (int j = 0; j < getWidth(); j++) {
    407             tableau.setEntry(dividendRow, j, tableau.getEntry(dividendRow, j) / divisor);
    408         }
    409     }
    410 
    411     /**
    412      * Subtracts a multiple of one row from another.
    413      * <p>
    414      * After application of this operation, the following will hold:
    415      *   minuendRow = minuendRow - multiple * subtrahendRow
    416      * </p>
    417      * @param minuendRow row index
    418      * @param subtrahendRow row index
    419      * @param multiple multiplication factor
    420      */
    421     protected void subtractRow(final int minuendRow, final int subtrahendRow,
    422                                final double multiple) {
    423         tableau.setRowVector(minuendRow, tableau.getRowVector(minuendRow)
    424             .subtract(tableau.getRowVector(subtrahendRow).mapMultiply(multiple)));
    425     }
    426 
    427     /**
    428      * Get the width of the tableau.
    429      * @return width of the tableau
    430      */
    431     protected final int getWidth() {
    432         return tableau.getColumnDimension();
    433     }
    434 
    435     /**
    436      * Get the height of the tableau.
    437      * @return height of the tableau
    438      */
    439     protected final int getHeight() {
    440         return tableau.getRowDimension();
    441     }
    442 
    443     /** Get an entry of the tableau.
    444      * @param row row index
    445      * @param column column index
    446      * @return entry at (row, column)
    447      */
    448     protected final double getEntry(final int row, final int column) {
    449         return tableau.getEntry(row, column);
    450     }
    451 
    452     /** Set an entry of the tableau.
    453      * @param row row index
    454      * @param column column index
    455      * @param value for the entry
    456      */
    457     protected final void setEntry(final int row, final int column,
    458                                   final double value) {
    459         tableau.setEntry(row, column, value);
    460     }
    461 
    462     /**
    463      * Get the offset of the first slack variable.
    464      * @return offset of the first slack variable
    465      */
    466     protected final int getSlackVariableOffset() {
    467         return getNumObjectiveFunctions() + numDecisionVariables;
    468     }
    469 
    470     /**
    471      * Get the offset of the first artificial variable.
    472      * @return offset of the first artificial variable
    473      */
    474     protected final int getArtificialVariableOffset() {
    475         return getNumObjectiveFunctions() + numDecisionVariables + numSlackVariables;
    476     }
    477 
    478     /**
    479      * Get the offset of the right hand side.
    480      * @return offset of the right hand side
    481      */
    482     protected final int getRhsOffset() {
    483         return getWidth() - 1;
    484     }
    485 
    486     /**
    487      * Get the number of decision variables.
    488      * <p>
    489      * If variables are not restricted to positive values, this will include 1
    490      * extra decision variable to represent the absolute value of the most
    491      * negative variable.
    492      * </p>
    493      * @return number of decision variables
    494      * @see #getOriginalNumDecisionVariables()
    495      */
    496     protected final int getNumDecisionVariables() {
    497         return numDecisionVariables;
    498     }
    499 
    500     /**
    501      * Get the original number of decision variables.
    502      * @return original number of decision variables
    503      * @see #getNumDecisionVariables()
    504      */
    505     protected final int getOriginalNumDecisionVariables() {
    506         return f.getCoefficients().getDimension();
    507     }
    508 
    509     /**
    510      * Get the number of slack variables.
    511      * @return number of slack variables
    512      */
    513     protected final int getNumSlackVariables() {
    514         return numSlackVariables;
    515     }
    516 
    517     /**
    518      * Get the number of artificial variables.
    519      * @return number of artificial variables
    520      */
    521     protected final int getNumArtificialVariables() {
    522         return numArtificialVariables;
    523     }
    524 
    525     /**
    526      * Get the tableau data.
    527      * @return tableau data
    528      */
    529     protected final double[][] getData() {
    530         return tableau.getData();
    531     }
    532 
    533     /** {@inheritDoc} */
    534     @Override
    535     public boolean equals(Object other) {
    536 
    537       if (this == other) {
    538         return true;
    539       }
    540 
    541       if (other instanceof SimplexTableau) {
    542           SimplexTableau rhs = (SimplexTableau) other;
    543           return (restrictToNonNegative  == rhs.restrictToNonNegative) &&
    544                  (numDecisionVariables   == rhs.numDecisionVariables) &&
    545                  (numSlackVariables      == rhs.numSlackVariables) &&
    546                  (numArtificialVariables == rhs.numArtificialVariables) &&
    547                  (epsilon                == rhs.epsilon) &&
    548                  f.equals(rhs.f) &&
    549                  constraints.equals(rhs.constraints) &&
    550                  tableau.equals(rhs.tableau);
    551       }
    552       return false;
    553     }
    554 
    555     /** {@inheritDoc} */
    556     @Override
    557     public int hashCode() {
    558         return Boolean.valueOf(restrictToNonNegative).hashCode() ^
    559                numDecisionVariables ^
    560                numSlackVariables ^
    561                numArtificialVariables ^
    562                Double.valueOf(epsilon).hashCode() ^
    563                f.hashCode() ^
    564                constraints.hashCode() ^
    565                tableau.hashCode();
    566     }
    567 
    568     /** Serialize the instance.
    569      * @param oos stream where object should be written
    570      * @throws IOException if object cannot be written to stream
    571      */
    572     private void writeObject(ObjectOutputStream oos)
    573         throws IOException {
    574         oos.defaultWriteObject();
    575         MatrixUtils.serializeRealMatrix(tableau, oos);
    576     }
    577 
    578     /** Deserialize the instance.
    579      * @param ois stream from which the object should be read
    580      * @throws ClassNotFoundException if a class in the stream cannot be found
    581      * @throws IOException if object cannot be read from the stream
    582      */
    583     private void readObject(ObjectInputStream ois)
    584       throws ClassNotFoundException, IOException {
    585         ois.defaultReadObject();
    586         MatrixUtils.deserializeRealMatrix(this, "tableau", ois);
    587     }
    588 
    589 }
    590