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
18package org.apache.commons.math.optimization.linear;
19
20import java.io.IOException;
21import java.io.ObjectInputStream;
22import java.io.ObjectOutputStream;
23import java.io.Serializable;
24import java.util.ArrayList;
25import java.util.Collection;
26import java.util.HashSet;
27import java.util.List;
28import java.util.Set;
29
30import org.apache.commons.math.linear.Array2DRowRealMatrix;
31import org.apache.commons.math.linear.MatrixUtils;
32import org.apache.commons.math.linear.RealMatrix;
33import org.apache.commons.math.linear.RealVector;
34import org.apache.commons.math.optimization.GoalType;
35import org.apache.commons.math.optimization.RealPointValuePair;
36import 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 */
63class 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