SimplexTableau.java

  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. package org.apache.commons.math3.optim.linear;

  18. import java.io.IOException;
  19. import java.io.ObjectInputStream;
  20. import java.io.ObjectOutputStream;
  21. import java.io.Serializable;
  22. import java.util.ArrayList;
  23. import java.util.Arrays;
  24. import java.util.Collection;
  25. import java.util.HashSet;
  26. import java.util.List;
  27. import java.util.Set;
  28. import java.util.TreeSet;

  29. import org.apache.commons.math3.linear.Array2DRowRealMatrix;
  30. import org.apache.commons.math3.linear.MatrixUtils;
  31. import org.apache.commons.math3.linear.RealVector;
  32. import org.apache.commons.math3.optim.nonlinear.scalar.GoalType;
  33. import org.apache.commons.math3.optim.PointValuePair;
  34. import org.apache.commons.math3.util.Precision;

  35. /**
  36.  * A tableau for use in the Simplex method.
  37.  *
  38.  * <p>
  39.  * Example:
  40.  * <pre>
  41.  *   W |  Z |  x1 |  x2 |  x- | s1 |  s2 |  a1 |  RHS
  42.  * ---------------------------------------------------
  43.  *  -1    0    0     0     0     0     0     1     0   &lt;= phase 1 objective
  44.  *   0    1   -15   -10    0     0     0     0     0   &lt;= phase 2 objective
  45.  *   0    0    1     0     0     1     0     0     2   &lt;= constraint 1
  46.  *   0    0    0     1     0     0     1     0     3   &lt;= constraint 2
  47.  *   0    0    1     1     0     0     0     1     4   &lt;= constraint 3
  48.  * </pre>
  49.  * W: Phase 1 objective function</br>
  50.  * Z: Phase 2 objective function</br>
  51.  * x1 &amp; x2: Decision variables</br>
  52.  * x-: Extra decision variable to allow for negative values</br>
  53.  * s1 &amp; s2: Slack/Surplus variables</br>
  54.  * a1: Artificial variable</br>
  55.  * RHS: Right hand side</br>
  56.  * </p>
  57.  * @since 2.0
  58.  */
  59. class SimplexTableau implements Serializable {

  60.     /** Column label for negative vars. */
  61.     private static final String NEGATIVE_VAR_COLUMN_LABEL = "x-";

  62.     /** Serializable version identifier. */
  63.     private static final long serialVersionUID = -1369660067587938365L;

  64.     /** Linear objective function. */
  65.     private final LinearObjectiveFunction f;

  66.     /** Linear constraints. */
  67.     private final List<LinearConstraint> constraints;

  68.     /** Whether to restrict the variables to non-negative values. */
  69.     private final boolean restrictToNonNegative;

  70.     /** The variables each column represents */
  71.     private final List<String> columnLabels = new ArrayList<String>();

  72.     /** Simple tableau. */
  73.     private transient Array2DRowRealMatrix tableau;

  74.     /** Number of decision variables. */
  75.     private final int numDecisionVariables;

  76.     /** Number of slack variables. */
  77.     private final int numSlackVariables;

  78.     /** Number of artificial variables. */
  79.     private int numArtificialVariables;

  80.     /** Amount of error to accept when checking for optimality. */
  81.     private final double epsilon;

  82.     /** Amount of error to accept in floating point comparisons. */
  83.     private final int maxUlps;

  84.     /** Maps basic variables to row they are basic in. */
  85.     private int[] basicVariables;

  86.     /** Maps rows to their corresponding basic variables. */
  87.     private int[] basicRows;

  88.     /**
  89.      * Builds a tableau for a linear problem.
  90.      *
  91.      * @param f Linear objective function.
  92.      * @param constraints Linear constraints.
  93.      * @param goalType Optimization goal: either {@link GoalType#MAXIMIZE}
  94.      * or {@link GoalType#MINIMIZE}.
  95.      * @param restrictToNonNegative Whether to restrict the variables to non-negative values.
  96.      * @param epsilon Amount of error to accept when checking for optimality.
  97.      */
  98.     SimplexTableau(final LinearObjectiveFunction f,
  99.                    final Collection<LinearConstraint> constraints,
  100.                    final GoalType goalType,
  101.                    final boolean restrictToNonNegative,
  102.                    final double epsilon) {
  103.         this(f, constraints, goalType, restrictToNonNegative, epsilon, SimplexSolver.DEFAULT_ULPS);
  104.     }

  105.     /**
  106.      * Build a tableau for a linear problem.
  107.      * @param f linear objective function
  108.      * @param constraints linear constraints
  109.      * @param goalType type of optimization goal: either {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}
  110.      * @param restrictToNonNegative whether to restrict the variables to non-negative values
  111.      * @param epsilon amount of error to accept when checking for optimality
  112.      * @param maxUlps amount of error to accept in floating point comparisons
  113.      */
  114.     SimplexTableau(final LinearObjectiveFunction f,
  115.                    final Collection<LinearConstraint> constraints,
  116.                    final GoalType goalType,
  117.                    final boolean restrictToNonNegative,
  118.                    final double epsilon,
  119.                    final int maxUlps) {
  120.         this.f                      = f;
  121.         this.constraints            = normalizeConstraints(constraints);
  122.         this.restrictToNonNegative  = restrictToNonNegative;
  123.         this.epsilon                = epsilon;
  124.         this.maxUlps                = maxUlps;
  125.         this.numDecisionVariables   = f.getCoefficients().getDimension() + (restrictToNonNegative ? 0 : 1);
  126.         this.numSlackVariables      = getConstraintTypeCounts(Relationship.LEQ) +
  127.                                       getConstraintTypeCounts(Relationship.GEQ);
  128.         this.numArtificialVariables = getConstraintTypeCounts(Relationship.EQ) +
  129.                                       getConstraintTypeCounts(Relationship.GEQ);
  130.         this.tableau = createTableau(goalType == GoalType.MAXIMIZE);
  131.         // initialize the basic variables for phase 1:
  132.         //   we know that only slack or artificial variables can be basic
  133.         initializeBasicVariables(getSlackVariableOffset());
  134.         initializeColumnLabels();
  135.     }

  136.     /**
  137.      * Initialize the labels for the columns.
  138.      */
  139.     protected void initializeColumnLabels() {
  140.       if (getNumObjectiveFunctions() == 2) {
  141.         columnLabels.add("W");
  142.       }
  143.       columnLabels.add("Z");
  144.       for (int i = 0; i < getOriginalNumDecisionVariables(); i++) {
  145.         columnLabels.add("x" + i);
  146.       }
  147.       if (!restrictToNonNegative) {
  148.         columnLabels.add(NEGATIVE_VAR_COLUMN_LABEL);
  149.       }
  150.       for (int i = 0; i < getNumSlackVariables(); i++) {
  151.         columnLabels.add("s" + i);
  152.       }
  153.       for (int i = 0; i < getNumArtificialVariables(); i++) {
  154.         columnLabels.add("a" + i);
  155.       }
  156.       columnLabels.add("RHS");
  157.     }

  158.     /**
  159.      * Create the tableau by itself.
  160.      * @param maximize if true, goal is to maximize the objective function
  161.      * @return created tableau
  162.      */
  163.     protected Array2DRowRealMatrix createTableau(final boolean maximize) {

  164.         // create a matrix of the correct size
  165.         int width = numDecisionVariables + numSlackVariables +
  166.         numArtificialVariables + getNumObjectiveFunctions() + 1; // + 1 is for RHS
  167.         int height = constraints.size() + getNumObjectiveFunctions();
  168.         Array2DRowRealMatrix matrix = new Array2DRowRealMatrix(height, width);

  169.         // initialize the objective function rows
  170.         if (getNumObjectiveFunctions() == 2) {
  171.             matrix.setEntry(0, 0, -1);
  172.         }

  173.         int zIndex = (getNumObjectiveFunctions() == 1) ? 0 : 1;
  174.         matrix.setEntry(zIndex, zIndex, maximize ? 1 : -1);
  175.         RealVector objectiveCoefficients = maximize ? f.getCoefficients().mapMultiply(-1) : f.getCoefficients();
  176.         copyArray(objectiveCoefficients.toArray(), matrix.getDataRef()[zIndex]);
  177.         matrix.setEntry(zIndex, width - 1, maximize ? f.getConstantTerm() : -1 * f.getConstantTerm());

  178.         if (!restrictToNonNegative) {
  179.             matrix.setEntry(zIndex, getSlackVariableOffset() - 1,
  180.                             getInvertedCoefficientSum(objectiveCoefficients));
  181.         }

  182.         // initialize the constraint rows
  183.         int slackVar = 0;
  184.         int artificialVar = 0;
  185.         for (int i = 0; i < constraints.size(); i++) {
  186.             LinearConstraint constraint = constraints.get(i);
  187.             int row = getNumObjectiveFunctions() + i;

  188.             // decision variable coefficients
  189.             copyArray(constraint.getCoefficients().toArray(), matrix.getDataRef()[row]);

  190.             // x-
  191.             if (!restrictToNonNegative) {
  192.                 matrix.setEntry(row, getSlackVariableOffset() - 1,
  193.                                 getInvertedCoefficientSum(constraint.getCoefficients()));
  194.             }

  195.             // RHS
  196.             matrix.setEntry(row, width - 1, constraint.getValue());

  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.             // artificial variables
  204.             if ((constraint.getRelationship() == Relationship.EQ) ||
  205.                 (constraint.getRelationship() == Relationship.GEQ)) {
  206.                 matrix.setEntry(0, getArtificialVariableOffset() + artificialVar, 1);
  207.                 matrix.setEntry(row, getArtificialVariableOffset() + artificialVar++, 1);
  208.                 matrix.setRowVector(0, matrix.getRowVector(0).subtract(matrix.getRowVector(row)));
  209.             }
  210.         }

  211.         return matrix;
  212.     }

  213.     /**
  214.      * Get new versions of the constraints which have positive right hand sides.
  215.      * @param originalConstraints original (not normalized) constraints
  216.      * @return new versions of the constraints
  217.      */
  218.     public List<LinearConstraint> normalizeConstraints(Collection<LinearConstraint> originalConstraints) {
  219.         List<LinearConstraint> normalized = new ArrayList<LinearConstraint>(originalConstraints.size());
  220.         for (LinearConstraint constraint : originalConstraints) {
  221.             normalized.add(normalize(constraint));
  222.         }
  223.         return normalized;
  224.     }

  225.     /**
  226.      * Get a new equation equivalent to this one with a positive right hand side.
  227.      * @param constraint reference constraint
  228.      * @return new equation
  229.      */
  230.     private LinearConstraint normalize(final LinearConstraint constraint) {
  231.         if (constraint.getValue() < 0) {
  232.             return new LinearConstraint(constraint.getCoefficients().mapMultiply(-1),
  233.                                         constraint.getRelationship().oppositeRelationship(),
  234.                                         -1 * constraint.getValue());
  235.         }
  236.         return new LinearConstraint(constraint.getCoefficients(),
  237.                                     constraint.getRelationship(), constraint.getValue());
  238.     }

  239.     /**
  240.      * Get the number of objective functions in this tableau.
  241.      * @return 2 for Phase 1.  1 for Phase 2.
  242.      */
  243.     protected final int getNumObjectiveFunctions() {
  244.         return this.numArtificialVariables > 0 ? 2 : 1;
  245.     }

  246.     /**
  247.      * Get a count of constraints corresponding to a specified relationship.
  248.      * @param relationship relationship to count
  249.      * @return number of constraint with the specified relationship
  250.      */
  251.     private int getConstraintTypeCounts(final Relationship relationship) {
  252.         int count = 0;
  253.         for (final LinearConstraint constraint : constraints) {
  254.             if (constraint.getRelationship() == relationship) {
  255.                 ++count;
  256.             }
  257.         }
  258.         return count;
  259.     }

  260.     /**
  261.      * Get the -1 times the sum of all coefficients in the given array.
  262.      * @param coefficients coefficients to sum
  263.      * @return the -1 times the sum of all coefficients in the given array.
  264.      */
  265.     protected static double getInvertedCoefficientSum(final RealVector coefficients) {
  266.         double sum = 0;
  267.         for (double coefficient : coefficients.toArray()) {
  268.             sum -= coefficient;
  269.         }
  270.         return sum;
  271.     }

  272.     /**
  273.      * Checks whether the given column is basic.
  274.      * @param col index of the column to check
  275.      * @return the row that the variable is basic in.  null if the column is not basic
  276.      */
  277.     protected Integer getBasicRow(final int col) {
  278.         final int row = basicVariables[col];
  279.         return row == -1 ? null : row;
  280.     }

  281.     /**
  282.      * Returns the variable that is basic in this row.
  283.      * @param row the index of the row to check
  284.      * @return the variable that is basic for this row.
  285.      */
  286.     protected int getBasicVariable(final int row) {
  287.         return basicRows[row];
  288.     }

  289.     /**
  290.      * Initializes the basic variable / row mapping.
  291.      * @param startColumn the column to start
  292.      */
  293.     private void initializeBasicVariables(final int startColumn) {
  294.         basicVariables = new int[getWidth() - 1];
  295.         basicRows = new int[getHeight()];

  296.         Arrays.fill(basicVariables, -1);

  297.         for (int i = startColumn; i < getWidth() - 1; i++) {
  298.             Integer row = findBasicRow(i);
  299.             if (row != null) {
  300.                 basicVariables[i] = row;
  301.                 basicRows[row] = i;
  302.             }
  303.         }
  304.     }

  305.     /**
  306.      * Returns the row in which the given column is basic.
  307.      * @param col index of the column
  308.      * @return the row that the variable is basic in, or {@code null} if the variable is not basic.
  309.      */
  310.     private Integer findBasicRow(final int col) {
  311.         Integer row = null;
  312.         for (int i = 0; i < getHeight(); i++) {
  313.             final double entry = getEntry(i, col);
  314.             if (Precision.equals(entry, 1d, maxUlps) && (row == null)) {
  315.                 row = i;
  316.             } else if (!Precision.equals(entry, 0d, maxUlps)) {
  317.                 return null;
  318.             }
  319.         }
  320.         return row;
  321.     }

  322.     /**
  323.      * Removes the phase 1 objective function, positive cost non-artificial variables,
  324.      * and the non-basic artificial variables from this tableau.
  325.      */
  326.     protected void dropPhase1Objective() {
  327.         if (getNumObjectiveFunctions() == 1) {
  328.             return;
  329.         }

  330.         final Set<Integer> columnsToDrop = new TreeSet<Integer>();
  331.         columnsToDrop.add(0);

  332.         // positive cost non-artificial variables
  333.         for (int i = getNumObjectiveFunctions(); i < getArtificialVariableOffset(); i++) {
  334.             final double entry = getEntry(0, i);
  335.             if (Precision.compareTo(entry, 0d, epsilon) > 0) {
  336.                 columnsToDrop.add(i);
  337.             }
  338.         }

  339.         // non-basic artificial variables
  340.         for (int i = 0; i < getNumArtificialVariables(); i++) {
  341.             int col = i + getArtificialVariableOffset();
  342.             if (getBasicRow(col) == null) {
  343.                 columnsToDrop.add(col);
  344.             }
  345.         }

  346.         final double[][] matrix = new double[getHeight() - 1][getWidth() - columnsToDrop.size()];
  347.         for (int i = 1; i < getHeight(); i++) {
  348.             int col = 0;
  349.             for (int j = 0; j < getWidth(); j++) {
  350.                 if (!columnsToDrop.contains(j)) {
  351.                     matrix[i - 1][col++] = getEntry(i, j);
  352.                 }
  353.             }
  354.         }

  355.         // remove the columns in reverse order so the indices are correct
  356.         Integer[] drop = columnsToDrop.toArray(new Integer[columnsToDrop.size()]);
  357.         for (int i = drop.length - 1; i >= 0; i--) {
  358.             columnLabels.remove((int) drop[i]);
  359.         }

  360.         this.tableau = new Array2DRowRealMatrix(matrix);
  361.         this.numArtificialVariables = 0;
  362.         // need to update the basic variable mappings as row/columns have been dropped
  363.         initializeBasicVariables(getNumObjectiveFunctions());
  364.     }

  365.     /**
  366.      * @param src the source array
  367.      * @param dest the destination array
  368.      */
  369.     private void copyArray(final double[] src, final double[] dest) {
  370.         System.arraycopy(src, 0, dest, getNumObjectiveFunctions(), src.length);
  371.     }

  372.     /**
  373.      * Returns whether the problem is at an optimal state.
  374.      * @return whether the model has been solved
  375.      */
  376.     boolean isOptimal() {
  377.         final double[] objectiveFunctionRow = getRow(0);
  378.         final int end = getRhsOffset();
  379.         for (int i = getNumObjectiveFunctions(); i < end; i++) {
  380.             final double entry = objectiveFunctionRow[i];
  381.             if (Precision.compareTo(entry, 0d, epsilon) < 0) {
  382.                 return false;
  383.             }
  384.         }
  385.         return true;
  386.     }

  387.     /**
  388.      * Get the current solution.
  389.      * @return current solution
  390.      */
  391.     protected PointValuePair getSolution() {
  392.         int negativeVarColumn = columnLabels.indexOf(NEGATIVE_VAR_COLUMN_LABEL);
  393.         Integer negativeVarBasicRow = negativeVarColumn > 0 ? getBasicRow(negativeVarColumn) : null;
  394.         double mostNegative = negativeVarBasicRow == null ? 0 : getEntry(negativeVarBasicRow, getRhsOffset());

  395.         final Set<Integer> usedBasicRows = new HashSet<Integer>();
  396.         final double[] coefficients = new double[getOriginalNumDecisionVariables()];
  397.         for (int i = 0; i < coefficients.length; i++) {
  398.             int colIndex = columnLabels.indexOf("x" + i);
  399.             if (colIndex < 0) {
  400.                 coefficients[i] = 0;
  401.                 continue;
  402.             }
  403.             Integer basicRow = getBasicRow(colIndex);
  404.             if (basicRow != null && basicRow == 0) {
  405.                 // if the basic row is found to be the objective function row
  406.                 // set the coefficient to 0 -> this case handles unconstrained
  407.                 // variables that are still part of the objective function
  408.                 coefficients[i] = 0;
  409.             } else if (usedBasicRows.contains(basicRow)) {
  410.                 // if multiple variables can take a given value
  411.                 // then we choose the first and set the rest equal to 0
  412.                 coefficients[i] = 0 - (restrictToNonNegative ? 0 : mostNegative);
  413.             } else {
  414.                 usedBasicRows.add(basicRow);
  415.                 coefficients[i] =
  416.                     (basicRow == null ? 0 : getEntry(basicRow, getRhsOffset())) -
  417.                     (restrictToNonNegative ? 0 : mostNegative);
  418.             }
  419.         }
  420.         return new PointValuePair(coefficients, f.value(coefficients));
  421.     }

  422.     /**
  423.      * Perform the row operations of the simplex algorithm with the selected
  424.      * pivot column and row.
  425.      * @param pivotCol the pivot column
  426.      * @param pivotRow the pivot row
  427.      */
  428.     protected void performRowOperations(int pivotCol, int pivotRow) {
  429.         // set the pivot element to 1
  430.         final double pivotVal = getEntry(pivotRow, pivotCol);
  431.         divideRow(pivotRow, pivotVal);

  432.         // set the rest of the pivot column to 0
  433.         for (int i = 0; i < getHeight(); i++) {
  434.             if (i != pivotRow) {
  435.                 final double multiplier = getEntry(i, pivotCol);
  436.                 if (multiplier != 0.0) {
  437.                     subtractRow(i, pivotRow, multiplier);
  438.                 }
  439.             }
  440.         }

  441.         // update the basic variable mappings
  442.         final int previousBasicVariable = getBasicVariable(pivotRow);
  443.         basicVariables[previousBasicVariable] = -1;
  444.         basicVariables[pivotCol] = pivotRow;
  445.         basicRows[pivotRow] = pivotCol;
  446.     }

  447.     /**
  448.      * Divides one row by a given divisor.
  449.      * <p>
  450.      * After application of this operation, the following will hold:
  451.      * <pre>dividendRow = dividendRow / divisor</pre>
  452.      *
  453.      * @param dividendRowIndex index of the row
  454.      * @param divisor value of the divisor
  455.      */
  456.     protected void divideRow(final int dividendRowIndex, final double divisor) {
  457.         final double[] dividendRow = getRow(dividendRowIndex);
  458.         for (int j = 0; j < getWidth(); j++) {
  459.             dividendRow[j] /= divisor;
  460.         }
  461.     }

  462.     /**
  463.      * Subtracts a multiple of one row from another.
  464.      * <p>
  465.      * After application of this operation, the following will hold:
  466.      * <pre>minuendRow = minuendRow - multiple * subtrahendRow</pre>
  467.      *
  468.      * @param minuendRowIndex row index
  469.      * @param subtrahendRowIndex row index
  470.      * @param multiplier multiplication factor
  471.      */
  472.     protected void subtractRow(final int minuendRowIndex, final int subtrahendRowIndex, final double multiplier) {
  473.         final double[] minuendRow = getRow(minuendRowIndex);
  474.         final double[] subtrahendRow = getRow(subtrahendRowIndex);
  475.         for (int i = 0; i < getWidth(); i++) {
  476.             minuendRow[i] -= subtrahendRow[i] * multiplier;
  477.         }
  478.     }

  479.     /**
  480.      * Get the width of the tableau.
  481.      * @return width of the tableau
  482.      */
  483.     protected final int getWidth() {
  484.         return tableau.getColumnDimension();
  485.     }

  486.     /**
  487.      * Get the height of the tableau.
  488.      * @return height of the tableau
  489.      */
  490.     protected final int getHeight() {
  491.         return tableau.getRowDimension();
  492.     }

  493.     /**
  494.      * Get an entry of the tableau.
  495.      * @param row row index
  496.      * @param column column index
  497.      * @return entry at (row, column)
  498.      */
  499.     protected final double getEntry(final int row, final int column) {
  500.         return tableau.getEntry(row, column);
  501.     }

  502.     /**
  503.      * Set an entry of the tableau.
  504.      * @param row row index
  505.      * @param column column index
  506.      * @param value for the entry
  507.      */
  508.     protected final void setEntry(final int row, final int column, final double value) {
  509.         tableau.setEntry(row, column, value);
  510.     }

  511.     /**
  512.      * Get the offset of the first slack variable.
  513.      * @return offset of the first slack variable
  514.      */
  515.     protected final int getSlackVariableOffset() {
  516.         return getNumObjectiveFunctions() + numDecisionVariables;
  517.     }

  518.     /**
  519.      * Get the offset of the first artificial variable.
  520.      * @return offset of the first artificial variable
  521.      */
  522.     protected final int getArtificialVariableOffset() {
  523.         return getNumObjectiveFunctions() + numDecisionVariables + numSlackVariables;
  524.     }

  525.     /**
  526.      * Get the offset of the right hand side.
  527.      * @return offset of the right hand side
  528.      */
  529.     protected final int getRhsOffset() {
  530.         return getWidth() - 1;
  531.     }

  532.     /**
  533.      * Get the number of decision variables.
  534.      * <p>
  535.      * If variables are not restricted to positive values, this will include 1 extra decision variable to represent
  536.      * the absolute value of the most negative variable.
  537.      *
  538.      * @return number of decision variables
  539.      * @see #getOriginalNumDecisionVariables()
  540.      */
  541.     protected final int getNumDecisionVariables() {
  542.         return numDecisionVariables;
  543.     }

  544.     /**
  545.      * Get the original number of decision variables.
  546.      * @return original number of decision variables
  547.      * @see #getNumDecisionVariables()
  548.      */
  549.     protected final int getOriginalNumDecisionVariables() {
  550.         return f.getCoefficients().getDimension();
  551.     }

  552.     /**
  553.      * Get the number of slack variables.
  554.      * @return number of slack variables
  555.      */
  556.     protected final int getNumSlackVariables() {
  557.         return numSlackVariables;
  558.     }

  559.     /**
  560.      * Get the number of artificial variables.
  561.      * @return number of artificial variables
  562.      */
  563.     protected final int getNumArtificialVariables() {
  564.         return numArtificialVariables;
  565.     }

  566.     /**
  567.      * Get the row from the tableau.
  568.      * @param row the row index
  569.      * @return the reference to the underlying row data
  570.      */
  571.     protected final double[] getRow(int row) {
  572.         return tableau.getDataRef()[row];
  573.     }

  574.     /**
  575.      * Get the tableau data.
  576.      * @return tableau data
  577.      */
  578.     protected final double[][] getData() {
  579.         return tableau.getData();
  580.     }

  581.     @Override
  582.     public boolean equals(Object other) {

  583.       if (this == other) {
  584.         return true;
  585.       }

  586.       if (other instanceof SimplexTableau) {
  587.           SimplexTableau rhs = (SimplexTableau) other;
  588.           return (restrictToNonNegative  == rhs.restrictToNonNegative) &&
  589.                  (numDecisionVariables   == rhs.numDecisionVariables) &&
  590.                  (numSlackVariables      == rhs.numSlackVariables) &&
  591.                  (numArtificialVariables == rhs.numArtificialVariables) &&
  592.                  (epsilon                == rhs.epsilon) &&
  593.                  (maxUlps                == rhs.maxUlps) &&
  594.                  f.equals(rhs.f) &&
  595.                  constraints.equals(rhs.constraints) &&
  596.                  tableau.equals(rhs.tableau);
  597.       }
  598.       return false;
  599.     }

  600.     @Override
  601.     public int hashCode() {
  602.         return Boolean.valueOf(restrictToNonNegative).hashCode() ^
  603.                numDecisionVariables ^
  604.                numSlackVariables ^
  605.                numArtificialVariables ^
  606.                Double.valueOf(epsilon).hashCode() ^
  607.                maxUlps ^
  608.                f.hashCode() ^
  609.                constraints.hashCode() ^
  610.                tableau.hashCode();
  611.     }

  612.     /**
  613.      * Serialize the instance.
  614.      * @param oos stream where object should be written
  615.      * @throws IOException if object cannot be written to stream
  616.      */
  617.     private void writeObject(ObjectOutputStream oos)
  618.         throws IOException {
  619.         oos.defaultWriteObject();
  620.         MatrixUtils.serializeRealMatrix(tableau, oos);
  621.     }

  622.     /**
  623.      * Deserialize the instance.
  624.      * @param ois stream from which the object should be read
  625.      * @throws ClassNotFoundException if a class in the stream cannot be found
  626.      * @throws IOException if object cannot be read from the stream
  627.      */
  628.     private void readObject(ObjectInputStream ois)
  629.       throws ClassNotFoundException, IOException {
  630.         ois.defaultReadObject();
  631.         MatrixUtils.deserializeRealMatrix(this, "tableau", ois);
  632.     }
  633. }