blob: c25619ab6ceb1397c6a720205009937e557aac39 [file] [log] [blame]
Raymonddee08492015-04-02 10:43:13 -07001/*
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}