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 <= phase 1 objective 47 * 0 1 -15 -10 0 0 0 0 0 <= phase 2 objective 48 * 0 0 1 0 0 1 0 0 2 <= constraint 1 49 * 0 0 0 1 0 0 1 0 3 <= constraint 2 50 * 0 0 1 1 0 0 0 1 4 <= constraint 3 51 * </pre> 52 * W: Phase 1 objective function</br> 53 * Z: Phase 2 objective function</br> 54 * x1 & x2: Decision variables</br> 55 * x-: Extra decision variable to allow for negative values</br> 56 * s1 & 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